Merge remote-tracking branch 'scalatest/new-framework-api-12' into 0.13

This commit is contained in:
Mark Harrah 2013-05-01 08:34:01 -04:00
commit f7ce8334c3
65 changed files with 1616 additions and 434 deletions

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
# Set default behaviour, in case users don't have core.autocrlf set.
* text=auto
# Explicitly declare text files we want to always be normalized and converted
# to native line endings on checkout.
*.scala text
*.java text

View File

@ -4,14 +4,14 @@
package sbt
import scala.collection.mutable
import org.scalatools.testing._
import testing._
import java.net.ServerSocket
import java.io._
import Tests.{Output => TestOutput, _}
import ForkMain._
private[sbt] object ForkTests {
def apply(frameworks: Seq[TestFramework], tests: List[TestDefinition], config: Execution, classpath: Seq[File], fork: ForkOptions, log: Logger): Task[TestOutput] = {
def apply(runners: Map[TestFramework, Runner], tests: List[TestDefinition], config: Execution, classpath: Seq[File], fork: ForkOptions, log: Logger): Task[TestOutput] = {
val opts = config.options.toList
val listeners = opts flatMap {
case Listeners(ls) => ls
@ -25,19 +25,13 @@ private[sbt] object ForkTests {
case Filter(f) => Some(f)
case _ => None
}
val argMap = frameworks.map {
f => f.implClassName -> opts.flatMap {
case Argument(None | Some(`f`), args) => args
case _ => Nil
}
}.toMap
std.TaskExtra.task {
if (!tests.isEmpty) {
val server = new ServerSocket(0)
object Acceptor extends Runnable {
val resultsAcc = mutable.Map.empty[String, TestResult.Value]
lazy val result = (overall(resultsAcc.values), resultsAcc.toMap)
val resultsAcc = mutable.Map.empty[String, SuiteResult]
lazy val result = TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty)
def run: Unit = {
val socket =
try {
@ -56,10 +50,12 @@ private[sbt] object ForkTests {
}.toArray
os.writeObject(testsFiltered)
os.writeInt(frameworks.size)
for ((clazz, args) <- argMap) {
os.writeObject(clazz)
os.writeObject(args.toArray)
os.writeInt(runners.size)
for ((testFramework, mainRunner) <- runners) {
val remoteArgs = mainRunner.remoteArgs()
os.writeObject(testFramework.implClassNames.toArray)
os.writeObject(mainRunner.args)
os.writeObject(remoteArgs)
}
os.flush()
@ -72,27 +68,32 @@ private[sbt] object ForkTests {
try {
testListeners.foreach(_.doInit())
new Thread(Acceptor).start()
val acceptorThread = new Thread(Acceptor)
acceptorThread.start()
val fullCp = classpath ++: Seq(IO.classLocationFile[ForkMain], IO.classLocationFile[Framework])
val options = Seq("-classpath", fullCp mkString File.pathSeparator, classOf[ForkMain].getCanonicalName, server.getLocalPort.toString)
val ec = Fork.java(fork, options)
val result =
if (ec != 0)
(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> TestResult.Error))
else
TestOutput(TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error), Iterable.empty)
else {
// Need to wait acceptor thread to finish its business
acceptorThread.join()
Acceptor.result
testListeners.foreach(_.doComplete(result._1))
}
testListeners.foreach(_.doComplete(result.overall))
result
} finally {
server.close()
}
} else
(TestResult.Passed, Map.empty[String, TestResult.Value])
TestOutput(TestResult.Passed, Map.empty[String, SuiteResult], Iterable.empty)
} tagw (config.tags: _*)
}
}
private final class React(is: ObjectInputStream, os: ObjectOutputStream, log: Logger, listeners: Seq[TestReportListener], results: mutable.Map[String, TestResult.Value])
private final class React(is: ObjectInputStream, os: ObjectOutputStream, log: Logger, listeners: Seq[TestReportListener], results: mutable.Map[String, SuiteResult])
{
import ForkTags._
@annotation.tailrec def react(): Unit = is.readObject match {
@ -106,9 +107,9 @@ private final class React(is: ObjectInputStream, os: ObjectOutputStream, log: Lo
listeners.foreach(_ startGroup group)
val event = TestEvent(tEvents)
listeners.foreach(_ testEvent event)
val result = event.result getOrElse TestResult.Passed
results += group -> result
listeners.foreach(_ endGroup (group, result))
val suiteResult = SuiteResult(tEvents)
results += group -> suiteResult
listeners.foreach(_ endGroup (group, suiteResult.result))
react()
}
}

View File

@ -11,7 +11,8 @@ package sbt
import xsbti.api.Definition
import ConcurrentRestrictions.Tag
import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint}
import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner, Task => TestTask}
import scala.annotation.tailrec
import java.io.File
@ -19,7 +20,8 @@ sealed trait TestOption
object Tests
{
// (overall result, individual results)
type Output = (TestResult.Value, Map[String,TestResult.Value])
final case class Output(overall: TestResult.Value, events: Map[String,SuiteResult], summaries: Iterable[Summary])
final case class Summary(name: String, summaryText: String)
final case class Setup(setup: ClassLoader => Unit) extends TestOption
def Setup(setup: () => Unit) = new Setup(_ => setup())
@ -43,7 +45,7 @@ object Tests
final case class Execution(options: Seq[TestOption], parallel: Boolean, tags: Seq[(Tag, Int)])
def apply(frameworks: Map[TestFramework, Framework], testLoader: ClassLoader, discovered: Seq[TestDefinition], config: Execution, log: Logger): Task[Output] =
def apply(frameworks: Map[TestFramework, Framework], testLoader: ClassLoader, runners: Map[TestFramework, Runner], discovered: Seq[TestDefinition], config: Execution, log: Logger): Task[Output] =
{
import collection.mutable.{HashSet, ListBuffer, Map, Set}
val testFilters = new ListBuffer[String => Boolean]
@ -58,7 +60,7 @@ object Tests
def frameworkArguments(framework: TestFramework, args: Seq[String]): Unit =
(frameworks get framework) match {
case Some(f) => frameworkArgs(f, args)
case None => undefinedFrameworks += framework.implClassName
case None => undefinedFrameworks ++= framework.implClassNames
}
for(option <- config.options)
@ -94,10 +96,10 @@ object Tests
val filtered0 = discovered.filter(includeTest).toList.distinct
val tests = if(orderedFilters.isEmpty) filtered0 else orderedFilters.flatMap(f => filtered0.filter(d => f(d.name))).toList.distinct
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, config)
testTask(testLoader, frameworks, runners, tests, setup.readOnly, cleanup.readOnly, log, testListeners.readOnly, arguments, config)
}
def testTask(frameworks: Seq[Framework], loader: ClassLoader, tests: Seq[TestDefinition],
def testTask(loader: ClassLoader, frameworks: Map[TestFramework, Framework], runners: Map[TestFramework, Runner], tests: Seq[TestDefinition],
userSetup: Iterable[ClassLoader => Unit], userCleanup: Iterable[ClassLoader => Unit],
log: Logger, testListeners: Seq[TestReportListener], arguments: Map[Framework, Seq[String]], config: Execution): Task[Output] =
{
@ -105,32 +107,68 @@ object Tests
def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map {a => () => a(loader) }
val (frameworkSetup, runnables, frameworkCleanup) =
TestFramework.testTasks(frameworks, loader, tests, log, testListeners, arguments)
TestFramework.testTasks(frameworks, runners, loader, tests, log, testListeners, arguments)
val setupTasks = fj(partApp(userSetup) :+ frameworkSetup)
val mainTasks =
if(config.parallel)
makeParallel(runnables, setupTasks, config.tags).toSeq.join
makeParallel(loader, runnables, setupTasks, config.tags)//.toSeq.join
else
makeSerial(runnables, setupTasks, config.tags)
makeSerial(loader, runnables, setupTasks, config.tags)
val taggedMainTasks = mainTasks.tagw(config.tags : _*)
taggedMainTasks map processResults flatMap { results =>
val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results._1))
val cleanupTasks = fj(partApp(userCleanup) :+ frameworkCleanup(results.overall))
cleanupTasks map { _ => results }
}
}
type TestRunnable = (String, () => TestResult.Value)
def makeParallel(runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) =
runnables map { case (name, test) => task { (name, test()) } tagw(tags : _*) dependsOn setupTasks named name }
def makeSerial(runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) =
task { runnables map { case (name, test) => (name, test()) } } dependsOn(setupTasks)
type TestRunnable = (String, TestFunction)
private def createNestedRunnables(name: String, loader: ClassLoader, testFun: TestFunction, nestedTasks: Seq[TestTask]): Seq[(String, TestFunction)] =
nestedTasks.view.zipWithIndex map { case (nt, idx) =>
(name, TestFramework.createTestFunction(loader, new TestDefinition(testFun.testDefinition.name + "-" + idx, testFun.testDefinition.fingerprint), testFun.runner, nt))
}
def processResults(results: Iterable[(String, TestResult.Value)]): (TestResult.Value, Map[String, TestResult.Value]) =
(overall(results.map(_._2)), results.toMap)
def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] =
def makeParallel(loader: ClassLoader, runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]): Task[Map[String,SuiteResult]] =
toTasks(loader, runnables.toSeq, tags).dependsOn(setupTasks)
def toTasks(loader: ClassLoader, runnables: Seq[TestRunnable], tags: Seq[(Tag,Int)]): Task[Map[String, SuiteResult]] = {
val tasks = runnables.map { case (name, test) => toTask(loader, name, test, tags) }
tasks.join.map( _.foldLeft(Map.empty[String, SuiteResult]) { case (sum, e) =>
sum ++ e
} )
}
def toTask(loader: ClassLoader, name: String, fun: TestFunction, tags: Seq[(Tag,Int)]): Task[Map[String, SuiteResult]] = {
val base = task { (name, fun.apply()) }
val taggedBase = base.tagw(tags : _*).tag(fun.tags.map(ConcurrentRestrictions.Tag(_)) : _*)
taggedBase flatMap { case (name, (result, nested)) =>
val nestedRunnables = createNestedRunnables(fun.testDefinition.name, loader, fun, nested)
toTasks(loader, nestedRunnables, tags).map( _.updated(name, result) )
}
}
def makeSerial(loader: ClassLoader, runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]): Task[List[(String, SuiteResult)]] =
{
@tailrec
def processRunnable(runnableList: List[TestRunnable], acc: List[(String, SuiteResult)]): List[(String, SuiteResult)] =
runnableList match {
case hd :: rst =>
val testFun = hd._2
val (result, nestedTasks) = testFun.apply()
val nestedRunnables = createNestedRunnables(testFun.testDefinition.name, loader, testFun, nestedTasks)
processRunnable(nestedRunnables.toList ::: rst, (hd._1, result) :: acc)
case Nil => acc
}
task { processRunnable(runnables.toList, List.empty) } dependsOn(setupTasks)
}
def processResults(results: Iterable[(String, SuiteResult)]): Output =
Output(overall(results.map(_._2.result)), results.toMap, Iterable.empty)
def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] =
if (parallel)
reduced(results.toIndexedSeq, {
case ((v1, m1), (v2, m2)) => (if (v1.id < v2.id) v2 else v1, m1 ++ m2)
case (Output(v1, m1, _), Output(v2, m2, _)) => Output(if (v1.id < v2.id) v2 else v1, m1 ++ m2, Iterable.empty)
})
else {
def sequence(tasks: List[Task[Output]], acc: List[Output]): Task[List[Output]] = tasks match {
@ -138,19 +176,19 @@ object Tests
case hd::tl => hd flatMap { out => sequence(tl, out::acc) }
}
sequence(results.toList, List()) map { ress =>
val (rs, ms) = ress.unzip
(overall(rs), ms reduce (_ ++ _))
val (rs, ms) = ress.unzip { e => (e.overall, e.events) }
Output(overall(rs), ms reduce (_ ++ _), Iterable.empty)
}
}
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, log: Logger): (Seq[TestDefinition], Set[String]) =
discover(frameworks flatMap TestFramework.getTests, allDefs(analysis), log)
discover(frameworks flatMap TestFramework.getFingerprints, allDefs(analysis), log)
def allDefs(analysis: Analysis) = analysis.apis.internal.values.flatMap(_.api.definitions).toSeq
def discover(fingerprints: Seq[Fingerprint], definitions: Seq[Definition], log: Logger): (Seq[TestDefinition], Set[String]) =
{
val subclasses = fingerprints collect { case sub: SubclassFingerprint => (sub.superClassName, sub.isModule, sub) };
val subclasses = fingerprints collect { case sub: SubclassFingerprint => (sub.superclassName, sub.isModule, sub) };
val annotations = fingerprints collect { case ann: AnnotatedFingerprint => (ann.annotationName, ann.isModule, ann) };
log.debug("Subclass fingerprints: " + subclasses)
log.debug("Annotation fingerprints: " + annotations)
@ -169,14 +207,45 @@ object Tests
(tests, mains.toSet)
}
def showResults(log: Logger, results: (TestResult.Value, Map[String, TestResult.Value]), noTestsMessage: =>String): Unit =
def showResults(log: Logger, results: Output, noTestsMessage: =>String): Unit =
{
if (results._2.isEmpty)
val multipleFrameworks = results.summaries.size > 1
def printSummary(name: String, message: String)
{
if (multipleFrameworks)
log.info(name)
if (message.size > 0)
log.info(message)
else
log.info("Summary for " + name + " not available.")
}
for (Summary(name, messages) <- results.summaries)
printSummary(name, messages)
val noSummary = results.summaries.headOption.forall(_.summaryText.size == 0)
val printStandard = multipleFrameworks || noSummary
// Print the standard one-liner statistic if no framework summary is defined, or when > 1 framework is in used.
if (printStandard)
{
val (skippedCount, errorsCount, passedCount, failuresCount) =
results.events.foldLeft((0, 0, 0, 0)) { case (acc, (name, testEvent)) =>
(acc._1 + testEvent.skippedCount, acc._2 + testEvent.errorCount, acc._3 + testEvent.passedCount, acc._4 + testEvent.failureCount)
}
val totalCount = failuresCount + errorsCount + skippedCount + passedCount
val postfix = "Total " + totalCount + ", Failed " + failuresCount + ", Errors " + errorsCount + ", Passed " + passedCount + ", Skipped " + skippedCount
results.overall match {
case TestResult.Error => log.error("Error: " + postfix)
case TestResult.Passed => log.info("Passed: " + postfix)
case TestResult.Failed => log.error("Failed: " + postfix)
}
}
// Let's always print out Failed tests for now
if (results.events.isEmpty)
log.info(noTestsMessage)
else {
import TestResult.{Error, Failed, Passed}
def select(Tpe: TestResult.Value) = results._2 collect { case (name, Tpe) => name }
def select(Tpe: TestResult.Value) = results.events collect { case (name, Tpe) => name }
val failures = select(Failed)
val errors = select(Error)
@ -184,17 +253,19 @@ object Tests
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", ""))
}
{
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)
throw new TestsFailedException
results.overall match {
case TestResult.Error | TestResult.Failed => throw new TestsFailedException
case TestResult.Passed =>
}
}

View File

@ -15,7 +15,7 @@ package sbt
import complete._
import std.TaskExtra._
import inc.{FileValueCache, Locate}
import org.scalatools.testing.{Framework, AnnotatedFingerprint, SubclassFingerprint}
import testing.{Framework, Runner, AnnotatedFingerprint, SubclassFingerprint}
import sys.error
import scala.xml.NodeSeq
@ -446,25 +446,46 @@ object Defaults extends BuildCommon
implicit val display = Project.showContextKey(state.value)
val modifiedOpts = Tests.Filters(filter(selected)) +: Tests.Argument(frameworkOptions : _*) +: config.options
val newConfig = config.copy(options = modifiedOpts)
val groupsTask = allTestGroupsTask(s, loadedTestFrameworks.value, testLoader.value, testGrouping.value, newConfig, fullClasspath.value, javaHome.value)
val output = allTestGroupsTask(s, loadedTestFrameworks.value, testLoader.value, testGrouping.value, newConfig, fullClasspath.value, javaHome.value)
val processed =
for(out <- groupsTask) yield
for(out <- output) yield
Tests.showResults(s.log, out, noTestsMessage(resolvedScoped.value))
Def.value(processed)
}
}
def createTestRunners(frameworks: Map[TestFramework,Framework], loader: ClassLoader, config: Tests.Execution) = {
import Tests.Argument
val opts = config.options.toList
frameworks.map { case (tf, f) =>
val args = opts.flatMap {
case Argument(None | Some(`tf`), args) => args
case _ => Nil
}
val mainRunner = f.runner(args.toArray, Array.empty[String], loader)
tf -> mainRunner
}
}
def allTestGroupsTask(s: TaskStreams, frameworks: Map[TestFramework,Framework], loader: ClassLoader, groups: Seq[Tests.Group], config: Tests.Execution, cp: Classpath, javaHome: Option[File]): Task[Tests.Output] = {
val runners = createTestRunners(frameworks, loader, config)
val groupTasks = groups map {
case Tests.Group(name, tests, runPolicy) =>
runPolicy match {
case Tests.SubProcess(opts) =>
ForkTests(frameworks.keys.toSeq, tests.toList, config, cp.files, opts, s.log) tag Tags.ForkedTestGroup
ForkTests(runners, tests.toList, config, cp.files, opts, s.log) tag Tags.ForkedTestGroup
case Tests.InProcess =>
Tests(frameworks, loader, tests, config, s.log)
Tests(frameworks, loader, runners, tests, config, s.log)
}
}
Tests.foldTasks(groupTasks, config.parallel)
val output = Tests.foldTasks(groupTasks, config.parallel)
output map { out =>
val summaries =
runners map { case (tf, r) =>
Tests.Summary(frameworks(tf).name, r.done())
}
out.copy(summaries = summaries)
}
}
def selectedFilter(args: Seq[String]): Seq[String => Boolean] =

View File

@ -14,7 +14,7 @@ package sbt
import scala.xml.{Node => XNode, NodeSeq}
import org.apache.ivy.core.module.{descriptor, id}
import descriptor.ModuleDescriptor, id.ModuleRevisionId
import org.scalatools.testing.Framework
import testing.Framework
import Configurations.CompilerPlugin
import Types.Id
import KeyRanks._

View File

@ -79,10 +79,10 @@ object Sbt extends Build
// Apache Ivy integration
lazy val ivySub = baseProject(file("ivy"), "Ivy") dependsOn(interfaceSub, launchInterfaceSub, crossSub, logSub % "compile;test->test", ioSub % "compile;test->test", launchSub % "test->test") settings(ivy, jsch, httpclient, testExclusive)
// Runner for uniform test interface
lazy val testingSub = baseProject(file("testing"), "Testing") dependsOn(ioSub, classpathSub, logSub, launchInterfaceSub, testAgentSub) settings(libraryDependencies += "org.scala-tools.testing" % "test-interface" % "0.5")
lazy val testingSub = baseProject(file("testing"), "Testing") dependsOn(ioSub, classpathSub, logSub, launchInterfaceSub, testAgentSub) settings(libraryDependencies += "org.scalatest" % "test-interface" % "1.0-SNAP3")
// Testing agent for running tests in a separate process.
lazy val testAgentSub = project(file("testing/agent"), "Test Agent") settings(
libraryDependencies += "org.scala-tools.testing" % "test-interface" % "0.5"
libraryDependencies += "org.scalatest" % "test-interface" % "1.0-SNAP3"
)
// Basic task engine

View File

@ -1,13 +1,13 @@
-> f
> project sub
> f
> reload
> f
$ copy-file changes/Changed.scala project/TestProject.scala
> reload
-> f
> project {external}root2
> g
# The current URI should be kept
> reload
-> f
> project sub
> f
> reload
> f
$ copy-file changes/Changed.scala project/TestProject.scala
> reload
-> f
> project {external}root2
> g
# The current URI should be kept
> reload
> g

View File

@ -1,10 +1,10 @@
package testCase;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface JFoo {
}
package testCase;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface JFoo {
}

View File

@ -1,7 +1,7 @@
import annotation.target.field
package object testCase {
type Foo = JFoo @field;
}
import annotation.target.field
package object testCase {
type Foo = JFoo @field;
}

View File

@ -1,26 +1,26 @@
import sbt._
import Keys._
object TestProject extends Build
{
lazy val root = Project("root", file(".")) settings(
ivyPaths <<= (baseDirectory, target)( (dir, t) => new IvyPaths(dir, Some(t / "ivy-cache"))),
libraryDependencies <++= baseDirectory (libraryDeps),
TaskKey[Unit]("check-forced") <<= check("1.2.14"),
TaskKey[Unit]("check-depend") <<= check("1.2.13")
)
def libraryDeps(base: File) = {
val slf4j = Seq("org.slf4j" % "slf4j-log4j12" % "1.1.0") // Uses log4j 1.2.13
if ((base / "force").exists) slf4j :+ ("log4j" % "log4j" % "1.2.14" force()) else slf4j
}
def check(ver: String) =
(dependencyClasspath in Compile) map { jars =>
val log4j = jars map (_.data) collect {
case f if f.getName contains "log4j-" => f.getName
}
if (log4j.size != 1 || !log4j.head.contains(ver))
error("Did not download the correct jar.")
}
}
import sbt._
import Keys._
object TestProject extends Build
{
lazy val root = Project("root", file(".")) settings(
ivyPaths <<= (baseDirectory, target)( (dir, t) => new IvyPaths(dir, Some(t / "ivy-cache"))),
libraryDependencies <++= baseDirectory (libraryDeps),
TaskKey[Unit]("check-forced") <<= check("1.2.14"),
TaskKey[Unit]("check-depend") <<= check("1.2.13")
)
def libraryDeps(base: File) = {
val slf4j = Seq("org.slf4j" % "slf4j-log4j12" % "1.1.0") // Uses log4j 1.2.13
if ((base / "force").exists) slf4j :+ ("log4j" % "log4j" % "1.2.14" force()) else slf4j
}
def check(ver: String) =
(dependencyClasspath in Compile) map { jars =>
val log4j = jars map (_.data) collect {
case f if f.getName contains "log4j-" => f.getName
}
if (log4j.size != 1 || !log4j.head.contains(ver))
error("Did not download the correct jar.")
}
}

View File

@ -1,12 +1,12 @@
$ touch force
> reload
> check-forced
-> check-depend
$ delete force
> reload
-> check-forced
$ touch force
> reload
> check-forced
-> check-depend
$ delete force
> reload
-> check-forced
> check-depend

View File

@ -1,4 +1,4 @@
k1 := {error("k1")}
k2 <<= k1 map identity
k1 := {error("k1")}
k2 <<= k1 map identity

View File

@ -1,4 +1,4 @@
k1 := {}
k2 <<= k1 map identity
k1 := {}
k2 <<= k1 map identity

View File

@ -1,6 +1,6 @@
k1 := {}
k2 := {}
k1 <<= k1 map {_ => error("k1")}
k1 := {}
k2 := {}
k1 <<= k1 map {_ => error("k1")}

View File

@ -1,6 +1,6 @@
k1 := {
}
k2 := {
}
k1 := {
}
k2 := {
}

View File

@ -1,8 +1,8 @@
import sbt._
object TestBuild extends Build {
val k1 = TaskKey[Unit]("k1")
val k2 = TaskKey[Unit]("k2")
lazy val root = Project("root", file("."))
}
import sbt._
object TestBuild extends Build {
val k1 = TaskKey[Unit]("k1")
val k2 = TaskKey[Unit]("k2")
lazy val root = Project("root", file("."))
}

View File

@ -1,25 +1,25 @@
> set k1 := {error("k1")}
> session save
> reload
-> k1
> set k2 <<= k1 map identity
> session save
> reload
-> k2
$ must-mirror build.sbt build.check.1
> set k1 := {}
> session save
> reload
> k1
> k2
$ must-mirror build.sbt build.check.2
> set k1 <<= k1 map {_ => error("k1")}
> set k2 := {}
> session save
> reload
-> k1
> k2
> set k1 := {error("k1")}
> session save
> reload
-> k1
> set k2 <<= k1 map identity
> session save
> reload
-> k2
$ must-mirror build.sbt build.check.1
> set k1 := {}
> session save
> reload
> k1
> k2
$ must-mirror build.sbt build.check.2
> set k1 <<= k1 map {_ => error("k1")}
> set k2 := {}
> session save
> reload
-> k1
> k2
$ must-mirror build.sbt build.check.3

View File

@ -0,0 +1,5 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")

View File

@ -0,0 +1,30 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends Reporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case SuiteStarting(_, suiteName, _, _, _, _, _, _, _, _) => writeFile("target/SuiteStarting-" + suiteName, suiteName)
case SuiteCompleted(_, suiteName, _, _, _, _, _, _, _, _, _) => writeFile("target/SuiteCompleted-" + suiteName, suiteName)
case TestStarting(_, _, _, _, testName, _, _, _, _, _, _, _) => writeFile("target/TestStarting-" + testName, testName)
case TestSucceeded(_, _, _, _, testName, _, _, _, _, _, _, _, _, _) => writeFile("target/TestSucceeded-" + testName, testName)
case _ =>
}
}
}

View File

@ -0,0 +1,12 @@
package com.test
import org.scalatest.Spec
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,13 @@
package com.test
import org.scalatest._
@DoNotDiscover
class TestSpec2 extends Spec {
def `TestSpec2-test-1 ` {}
def `TestSpec2-test-2 ` {}
def `TestSpec2-test-3 ` {}
}

View File

@ -0,0 +1,72 @@
#Test the ScalaTest framework to exclude suite annotated with @DoNotDiscover (TestSpec2) properly.
#A CustomReporter is used to report expected ScalaTest's events by writing out to files in target/,
#it is then used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
> clean
> test
$ exists target/SuiteStarting-TestSpec
$ absent target/SuiteStarting-TestSpec-2
$ exists target/SuiteCompleted-TestSpec
$ absent target/SuiteCompleted-TestSpec-2
$ absent target/SuiteStarting-TestSpec2
$ absent target/SuiteStarting-TestSpec2-2
$ absent target/SuiteCompleted-TestSpec2
$ absent target/SuiteCompleted-TestSpec2-2
$ exists target/TestStarting-TestSpec-test-1
$ absent target/TestStarting-TestSpec-test-1-2
$ exists target/TestSucceeded-TestSpec-test-1
$ absent target/TestSucceeded-TestSpec-test-1-2
$ exists target/TestStarting-TestSpec-test-2
$ absent target/TestStarting-TestSpec-test-2-2
$ exists target/TestSucceeded-TestSpec-test-2
$ absent target/TestSucceeded-TestSpec-test-2-2
$ exists target/TestStarting-TestSpec-test-3
$ absent target/TestStarting-TestSpec-test-3-2
$ exists target/TestSucceeded-TestSpec-test-3
$ absent target/TestSucceeded-TestSpec-test-3-2
$ absent target/TestStarting-TestSpec2-test-1
$ absent target/TestStarting-TestSpec2-test-1-2
$ absent target/TestSucceeded-TestSpec2-test-1
$ absent target/TestSucceeded-TestSpec2-test-1-2
$ absent target/TestStarting-TestSpec2-test-2
$ absent target/TestStarting-TestSpec2-test-2-2
$ absent target/TestSucceeded-TestSpec2-test-2
$ absent target/TestSucceeded-TestSpec2-test-2-2
$ absent target/TestStarting-TestSpec2-test-3
$ absent target/TestStarting-TestSpec2-test-3-2
$ absent target/TestSucceeded-TestSpec2-test-3
$ absent target/TestSucceeded-TestSpec2-test-3-2

View File

@ -0,0 +1,5 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")

View File

@ -0,0 +1,37 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends ResourcefulReporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case runCompleted: RunCompleted => writeFile("target/RunCompleted", "RunCompleted")
case _ =>
}
}
def dispose() {
val file = new File("target/dispose")
val filePath =
if (file.exists)
"target/dispose2"
else
"target/dispose"
writeFile(filePath, "dispose")
}
}

View File

@ -0,0 +1,12 @@
package com.test
import org.scalatest.Spec
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,12 @@
package com.test
import org.scalatest.Spec
class TestSpec2 extends Spec {
def `TestSpec2-test-1 ` {}
def `TestSpec2-test-2 ` {}
def `TestSpec2-test-3 ` {}
}

View File

@ -0,0 +1,19 @@
#Test the framework will call Runner.done once.
#Because ScalaTest's runner will report RunCompleted when the run completed, a CustomReporter is
#used to report expected ScalaTest's RunCompleted event by writing out to target/, it is then
#used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
#ResourcefulReporter's dispose method will be called in Runner.done also, and it should be called
#once only.
> clean
> test
$ exists target/RunCompleted
$ absent target/RunCompleted-2
$ exists target/dispose
$ absent target/dispose2

View File

@ -0,0 +1,7 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")
parallelExecution in Test := true

View File

@ -0,0 +1,30 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends Reporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case SuiteStarting(_, suiteName, _, _, _, _, _, _, _, _) => writeFile("target/SuiteStarting-" + suiteName, suiteName)
case SuiteCompleted(_, suiteName, _, _, _, _, _, _, _, _, _) => writeFile("target/SuiteCompleted-" + suiteName, suiteName)
case TestStarting(_, _, _, _, testName, _, _, _, _, _, _, _) => writeFile("target/TestStarting-" + testName, testName)
case TestSucceeded(_, _, _, _, testName, _, _, _, _, _, _, _, _, _) => writeFile("target/TestSucceeded-" + testName, testName)
case _ =>
}
}
}

View File

@ -0,0 +1,17 @@
package com.test
import org.scalatest._
class NestedSpecs extends Suites (
new TestSpec
)
@DoNotDiscover
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,48 @@
#This test that the framework will execute ScalaTest nested suites as parallel nested task (InProcess) properly.
#A CustomReporter is used to report expected ScalaTest's events by writing out to files in target/,
#it is then used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
> clean
> test
$ exists target/SuiteStarting-NestedSpecs
$ absent target/SuiteStarting-NestedSpecs-2
$ exists target/SuiteCompleted-NestedSpecs
$ absent target/SuiteCompleted-NestedSpecs-2
$ exists target/SuiteStarting-TestSpec
$ absent target/SuiteStarting-TestSpec-2
$ exists target/SuiteCompleted-TestSpec
$ absent target/SuiteCompleted-TestSpec-2
$ exists target/TestStarting-TestSpec-test-1
$ absent target/TestStarting-TestSpec-test-1-2
$ exists target/TestSucceeded-TestSpec-test-1
$ absent target/TestSucceeded-TestSpec-test-1-2
$ exists target/TestStarting-TestSpec-test-2
$ absent target/TestStarting-TestSpec-test-2-2
$ exists target/TestSucceeded-TestSpec-test-2
$ absent target/TestSucceeded-TestSpec-test-2-2
$ exists target/TestStarting-TestSpec-test-3
$ absent target/TestStarting-TestSpec-test-3-2
$ exists target/TestSucceeded-TestSpec-test-3
$ absent target/TestSucceeded-TestSpec-test-3-2

View File

@ -0,0 +1,7 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")
parallelExecution in Test := false

View File

@ -0,0 +1,30 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends Reporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case SuiteStarting(_, suiteName, _, _, _, _, _, _, _, _) => writeFile("target/SuiteStarting-" + suiteName, suiteName)
case SuiteCompleted(_, suiteName, _, _, _, _, _, _, _, _, _) => writeFile("target/SuiteCompleted-" + suiteName, suiteName)
case TestStarting(_, _, _, _, testName, _, _, _, _, _, _, _) => writeFile("target/TestStarting-" + testName, testName)
case TestSucceeded(_, _, _, _, testName, _, _, _, _, _, _, _, _, _) => writeFile("target/TestSucceeded-" + testName, testName)
case _ =>
}
}
}

View File

@ -0,0 +1,17 @@
package com.test
import org.scalatest._
class NestedSpecs extends Suites (
new TestSpec
)
@DoNotDiscover
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,48 @@
#This test that the framework will execute ScalaTest nested suites as sequential nested task (InProcess) properly.
#A CustomReporter is used to report expected ScalaTest's events by writing out to files in target/,
#it is then used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
> clean
> test
$ exists target/SuiteStarting-NestedSpecs
$ absent target/SuiteStarting-NestedSpecs-2
$ exists target/SuiteCompleted-NestedSpecs
$ absent target/SuiteCompleted-NestedSpecs-2
$ exists target/SuiteStarting-TestSpec
$ absent target/SuiteStarting-TestSpec-2
$ exists target/SuiteCompleted-TestSpec
$ absent target/SuiteCompleted-TestSpec-2
$ exists target/TestStarting-TestSpec-test-1
$ absent target/TestStarting-TestSpec-test-1-2
$ exists target/TestSucceeded-TestSpec-test-1
$ absent target/TestSucceeded-TestSpec-test-1-2
$ exists target/TestStarting-TestSpec-test-2
$ absent target/TestStarting-TestSpec-test-2-2
$ exists target/TestSucceeded-TestSpec-test-2
$ absent target/TestSucceeded-TestSpec-test-2-2
$ exists target/TestStarting-TestSpec-test-3
$ absent target/TestStarting-TestSpec-test-3-2
$ exists target/TestSucceeded-TestSpec-test-3
$ absent target/TestSucceeded-TestSpec-test-3-2

View File

@ -0,0 +1,7 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")
fork := true

View File

@ -0,0 +1,30 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends Reporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case SuiteStarting(_, suiteName, _, _, _, _, _, _, _, _) => writeFile("target/SuiteStarting-" + suiteName, suiteName)
case SuiteCompleted(_, suiteName, _, _, _, _, _, _, _, _, _) => writeFile("target/SuiteCompleted-" + suiteName, suiteName)
case TestStarting(_, _, _, _, testName, _, _, _, _, _, _, _) => writeFile("target/TestStarting-" + testName, testName)
case TestSucceeded(_, _, _, _, testName, _, _, _, _, _, _, _, _, _) => writeFile("target/TestSucceeded-" + testName, testName)
case _ =>
}
}
}

View File

@ -0,0 +1,17 @@
package com.test
import org.scalatest._
class NestedSpecs extends Suites (
new TestSpec
)
@DoNotDiscover
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,48 @@
#This test that the framework will execute ScalaTest nested suites as sequential nested task (SubProcess) properly.
#A CustomReporter is used to report expected ScalaTest's events by writing out to files in target/,
#it is then used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
> clean
> test
$ exists target/SuiteStarting-NestedSpecs
$ absent target/SuiteStarting-NestedSpecs-2
$ exists target/SuiteCompleted-NestedSpecs
$ absent target/SuiteCompleted-NestedSpecs-2
$ exists target/SuiteStarting-TestSpec
$ absent target/SuiteStarting-TestSpec-2
$ exists target/SuiteCompleted-TestSpec
$ absent target/SuiteCompleted-TestSpec-2
$ exists target/TestStarting-TestSpec-test-1
$ absent target/TestStarting-TestSpec-test-1-2
$ exists target/TestSucceeded-TestSpec-test-1
$ absent target/TestSucceeded-TestSpec-test-1-2
$ exists target/TestStarting-TestSpec-test-2
$ absent target/TestStarting-TestSpec-test-2-2
$ exists target/TestSucceeded-TestSpec-test-2
$ absent target/TestSucceeded-TestSpec-test-2-2
$ exists target/TestStarting-TestSpec-test-3
$ absent target/TestStarting-TestSpec-test-3-2
$ exists target/TestSucceeded-TestSpec-test-3
$ absent target/TestSucceeded-TestSpec-test-3-2

View File

@ -0,0 +1,5 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")

View File

@ -0,0 +1,27 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends Reporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case runStarting: RunStarting => writeFile("target/RunStarting", "RunStarting")
case _ =>
}
}
}

View File

@ -0,0 +1,12 @@
package com.test
import org.scalatest.Spec
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,12 @@
package com.test
import org.scalatest.Spec
class TestSpec2 extends Spec {
def `TestSpec2-test-1 ` {}
def `TestSpec2-test-2 ` {}
def `TestSpec2-test-3 ` {}
}

View File

@ -0,0 +1,13 @@
#This test that the framework will only use a single runner instance to execute all tests.
#Because ScalaTest's runner will report RunStarting when the run start, a CustomReporter is
#used to report expected ScalaTest's RunStarting event by writing out to target/, it is then
#used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
> clean
> test
$ exists target/RunStarting
$ absent target/RunStarting-2

View File

@ -14,10 +14,10 @@ object Ticket543Test extends Build {
fork := true,
testListeners += new TestReportListener {
def testEvent(event: TestEvent) {
for (e <- event.detail.filter(_.result == org.scalatools.testing.Result.Failure)) {
if (e.error ne null) {
for (e <- event.detail.filter(_.status == sbt.testing.Status.Failure)) {
if (e.throwable ne null) {
val caw = new CharArrayWriter
e.error.printStackTrace(new PrintWriter(caw))
e.throwable.printStackTrace(new PrintWriter(caw))
if (caw.toString.contains("Test.scala:"))
marker.createNewFile()
}

View File

@ -0,0 +1,5 @@
scalaVersion := "2.10.1"
libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15"
testOptions in Test += Tests.Argument("-r", "custom.CustomReporter")

View File

@ -0,0 +1,30 @@
package custom
import java.io._
import org.scalatest._
import events._
class CustomReporter extends Reporter {
private def writeFile(filePath: String, content: String) {
val file = new File(filePath)
val writer =
if (!file.exists)
new FileWriter(new File(filePath))
else
new FileWriter(new File(filePath + "-2"))
writer.write(content)
writer.flush()
writer.close()
}
def apply(event: Event) {
event match {
case SuiteStarting(_, suiteName, _, _, _, _, _, _, _, _) => writeFile("target/SuiteStarting-" + suiteName, suiteName)
case SuiteCompleted(_, suiteName, _, _, _, _, _, _, _, _, _) => writeFile("target/SuiteCompleted-" + suiteName, suiteName)
case TestStarting(_, _, _, _, testName, _, _, _, _, _, _, _) => writeFile("target/TestStarting-" + testName, testName)
case TestSucceeded(_, _, _, _, testName, _, _, _, _, _, _, _, _, _) => writeFile("target/TestSucceeded-" + testName, testName)
case _ =>
}
}
}

View File

@ -0,0 +1,12 @@
package com.test
import org.scalatest.Spec
class TestSpec extends Spec {
def `TestSpec-test-1 ` {}
def `TestSpec-test-2 ` {}
def `TestSpec-test-3 ` {}
}

View File

@ -0,0 +1,12 @@
package com.test
import org.scalatest.Spec
class TestSpec2 extends Spec {
def `TestSpec2-test-1 ` {}
def `TestSpec2-test-2 ` {}
def `TestSpec2-test-3 ` {}
}

View File

@ -0,0 +1,72 @@
#This test that the framework will execute ScalaTest suites as task properly.
#A CustomReporter is used to report expected ScalaTest's events by writing out to files in target/,
#it is then used to check for their existence, and if the expected event is fired > 1 (which is unexpected),
#a xxxx-2 file will be written, thus here we also check for 'absent' of such file.
> clean
> test
$ exists target/SuiteStarting-TestSpec
$ absent target/SuiteStarting-TestSpec-2
$ exists target/SuiteCompleted-TestSpec
$ absent target/SuiteCompleted-TestSpec-2
$ exists target/SuiteStarting-TestSpec2
$ absent target/SuiteStarting-TestSpec2-2
$ exists target/SuiteCompleted-TestSpec2
$ absent target/SuiteCompleted-TestSpec2-2
$ exists target/TestStarting-TestSpec-test-1
$ absent target/TestStarting-TestSpec-test-1-2
$ exists target/TestSucceeded-TestSpec-test-1
$ absent target/TestSucceeded-TestSpec-test-1-2
$ exists target/TestStarting-TestSpec-test-2
$ absent target/TestStarting-TestSpec-test-2-2
$ exists target/TestSucceeded-TestSpec-test-2
$ absent target/TestSucceeded-TestSpec-test-2-2
$ exists target/TestStarting-TestSpec-test-3
$ absent target/TestStarting-TestSpec-test-3-2
$ exists target/TestSucceeded-TestSpec-test-3
$ absent target/TestSucceeded-TestSpec-test-3-2
$ exists target/TestStarting-TestSpec2-test-1
$ absent target/TestStarting-TestSpec2-test-1-2
$ exists target/TestSucceeded-TestSpec2-test-1
$ absent target/TestSucceeded-TestSpec2-test-1-2
$ exists target/TestStarting-TestSpec2-test-2
$ absent target/TestStarting-TestSpec2-test-2-2
$ exists target/TestSucceeded-TestSpec2-test-2
$ absent target/TestSucceeded-TestSpec2-test-2-2
$ exists target/TestStarting-TestSpec2-test-3
$ absent target/TestStarting-TestSpec2-test-3-2
$ exists target/TestSucceeded-TestSpec2-test-3
$ absent target/TestSucceeded-TestSpec2-test-3-2

View File

@ -1,4 +1,4 @@
case class A(b: B) {
def foo = b.foo
// A comment added should trigger recompilation.
case class A(b: B) {
def foo = b.foo
// A comment added should trigger recompilation.
}

View File

@ -1,5 +1,5 @@
class B {
def foo = 1
// API-level change
def bar(a: A) = 2
}
class B {
def foo = 1
// API-level change
def bar(a: A) = 2
}

View File

@ -1,7 +1,7 @@
import java.io.File
trait Base {
val marker = new File("marker")
// Test compilation group change.
val baz = ""
}
import java.io.File
trait Base {
val marker = new File("marker")
// Test compilation group change.
val baz = ""
}

View File

@ -1,4 +1,4 @@
// A, B are referring to each other, OK in the same compilation group.
case class A(b: B) {
def foo = b.foo
// A, B are referring to each other, OK in the same compilation group.
case class A(b: B) {
def foo = b.foo
}

View File

@ -1,4 +1,4 @@
class B {
def foo = 1
def bar(a: A) {}
}
class B {
def foo = 1
def bar(a: A) {}
}

View File

@ -1,5 +1,5 @@
import java.io.File
trait Base {
val marker = new File("marker")
}
import java.io.File
trait Base {
val marker = new File("marker")
}

View File

@ -1,11 +1,11 @@
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
class Create extends FlatSpec with ShouldMatchers with Base {
"a file" should "not exist" in {
A(new B).foo
marker.exists should equal(false)
marker.createNewFile() should equal (true)
}
}
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
class Create extends FlatSpec with ShouldMatchers with Base {
"a file" should "not exist" in {
A(new B).foo
marker.exists should equal(false)
marker.createNewFile() should equal (true)
}
}

View File

@ -1,10 +1,10 @@
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
class Delete extends FlatSpec with ShouldMatchers with Base {
"a file" should "exist" in {
marker.exists should equal(true)
marker.delete()
}
}
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
class Delete extends FlatSpec with ShouldMatchers with Base {
"a file" should "exist" in {
marker.exists should equal(true)
marker.delete()
}
}

View File

@ -1,34 +1,34 @@
> test-quick Create
# Create not re-run, Delete deletes the file.
> test-quick
# Re-create the file.
> test-only Create
# Non-API change
$ copy-file changed/A.scala src/main/scala/A.scala
> compile
$ sleep 2000
# Create is run. Delete is not since it doesn't have src/main dependency.
-> test-quick
> test-only Delete
# Previous run of Create failed, re-run.
> test-quick Create
# No-op.
> test-quick Create
# API change.
$ copy-file changed/B.scala src/main/scala/B.scala
> compile
$ sleep 2000
-> test-quick Create
> test-only Delete
# Previous run of Create failed, re-run.
> test-quick Create
# src/test compilation group change.
$ copy-file changed/Base.scala src/test/scala/Base.scala
> test:compile
$ sleep 2000
-> test-quick Create
> test-quick Delete
> test-quick Create
# Create not re-run, Delete deletes the file.
> test-quick
# Re-create the file.
> test-only Create
# Non-API change
$ copy-file changed/A.scala src/main/scala/A.scala
> compile
$ sleep 2000
# Create is run. Delete is not since it doesn't have src/main dependency.
-> test-quick
> test-only Delete
# Previous run of Create failed, re-run.
> test-quick Create
# No-op.
> test-quick Create
# API change.
$ copy-file changed/B.scala src/main/scala/B.scala
> compile
$ sleep 2000
-> test-quick Create
> test-only Delete
# Previous run of Create failed, re-run.
> test-quick Create
# src/test compilation group change.
$ copy-file changed/Base.scala src/test/scala/Base.scala
> test:compile
$ sleep 2000
-> test-quick Create
> test-quick Delete
> test-quick Create

View File

@ -3,7 +3,7 @@
*/
package sbt;
import org.scalatools.testing.*;
import sbt.testing.*;
import java.io.IOException;
import java.io.ObjectInputStream;
@ -13,17 +13,21 @@ import java.net.Socket;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
public class ForkMain {
static class SubclassFingerscan implements TestFingerprint, Serializable {
static class SubclassFingerscan implements SubclassFingerprint, Serializable {
private boolean isModule;
private String superClassName;
private String superclassName;
private boolean requireNoArgConstructor;
SubclassFingerscan(SubclassFingerprint print) {
isModule = print.isModule();
superClassName = print.superClassName();
superclassName = print.superclassName();
requireNoArgConstructor = print.requireNoArgConstructor();
}
public boolean isModule() { return isModule; }
public String superClassName() { return superClassName; }
public String superclassName() { return superclassName; }
public boolean requireNoArgConstructor() { return requireNoArgConstructor; }
}
static class AnnotatedFingerscan implements AnnotatedFingerprint, Serializable {
private boolean isModule;
@ -59,21 +63,69 @@ public class ForkMain {
public String getMessage() { return originalMessage; }
public Exception getCause() { return cause; }
}
static class ForkEvent implements Event, Serializable {
static class ForkSelector extends Selector implements Serializable {}
static class ForkSuiteSelector extends ForkSelector {}
static class ForkTestSelector extends ForkSelector {
private String testName;
private String description;
private Result result;
private Throwable error;
ForkEvent(Event e) {
testName = e.testName();
description = e.description();
result = e.result();
if (e.error() != null) error = new ForkError(e.error());
ForkTestSelector(TestSelector testSelector) {
this.testName = testSelector.getTestName();
}
public String getTestName() {
return testName;
}
}
static class ForkNestedSuiteSelector extends ForkSelector {
private String suiteId;
ForkNestedSuiteSelector(NestedSuiteSelector nestedSuiteSelector) {
this.suiteId = nestedSuiteSelector.getSuiteId();
}
public String getSuiteId() {
return suiteId;
}
}
static class ForkNestedTestSelector extends ForkSelector {
private String suiteId;
private String testName;
ForkNestedTestSelector(NestedTestSelector nestedTestSelector) {
this.suiteId = nestedTestSelector.getSuiteId();
this.testName = nestedTestSelector.getTestName();
}
public String getSuiteId() {
return suiteId;
}
public String getTestName() {
return testName;
}
}
static class ForkEvent implements Event, Serializable {
private String fullyQualifiedName;
private boolean isModule;
private ForkSelector selector;
private Status status;
private Throwable throwable;
ForkEvent(Event e) {
fullyQualifiedName = e.fullyQualifiedName();
isModule = e.isModule();
selector = forkSelector(e.selector());
status = e.status();
if (e.throwable() != null) throwable = new ForkError(e.throwable());
}
public String fullyQualifiedName() { return fullyQualifiedName; }
public boolean isModule() { return isModule; }
public Selector selector() { return selector; }
public Status status() { return status; }
public Throwable throwable() { return throwable; }
protected ForkSelector forkSelector(Selector selector) {
if (selector instanceof SuiteSelector)
return new ForkSuiteSelector();
else if (selector instanceof TestSelector)
return new ForkTestSelector((TestSelector) selector);
else if (selector instanceof NestedSuiteSelector)
return new ForkNestedSuiteSelector((NestedSuiteSelector) selector);
else
return new ForkNestedTestSelector((NestedTestSelector) selector);
}
public String testName() { return testName; }
public String description() { return description; }
public Result result() { return result; }
public Throwable error() { return error; }
}
public static void main(String[] args) throws Exception {
Socket socket = new Socket(InetAddress.getByName(null), Integer.valueOf(args[0]));
@ -95,7 +147,7 @@ public class ForkMain {
if (f1 instanceof SubclassFingerprint && f2 instanceof SubclassFingerprint) {
final SubclassFingerprint sf1 = (SubclassFingerprint) f1;
final SubclassFingerprint sf2 = (SubclassFingerprint) f2;
return sf1.isModule() == sf2.isModule() && sf1.superClassName().equals(sf2.superClassName());
return sf1.isModule() == sf2.isModule() && sf1.superclassName().equals(sf2.superclassName());
} else if (f1 instanceof AnnotatedFingerprint && f2 instanceof AnnotatedFingerprint) {
AnnotatedFingerprint af1 = (AnnotatedFingerprint) f1;
AnnotatedFingerprint af2 = (AnnotatedFingerprint) f2;
@ -106,7 +158,7 @@ public class ForkMain {
class RunAborted extends RuntimeException {
RunAborted(Exception e) { super(e); }
}
void write(ObjectOutputStream os, Object obj) {
synchronized void write(ObjectOutputStream os, Object obj) {
try {
os.writeObject(obj);
os.flush();
@ -117,6 +169,9 @@ public class ForkMain {
void logError(ObjectOutputStream os, String message) {
write(os, new Object[]{ForkTags.Error, message});
}
void logDebug(ObjectOutputStream os, String message) {
write(os, new Object[]{ForkTags.Debug, message});
}
void writeEvents(ObjectOutputStream os, ForkTestDefinition test, ForkEvent[] events) {
write(os, new Object[]{test.name, events});
}
@ -136,50 +191,98 @@ public class ForkMain {
};
for (int i = 0; i < nFrameworks; i++) {
final String implClassName = (String) is.readObject();
final String[] implClassNames = (String[]) is.readObject();
final String[] frameworkArgs = (String[]) is.readObject();
final String[] remoteFrameworkArgs = (String[]) is.readObject();
final Framework framework;
try {
framework = (Framework) Class.forName(implClassName).newInstance();
} catch (ClassNotFoundException e) {
logError(os, "Framework implementation '" + implClassName + "' not present.");
continue;
Framework framework = null;
for (String implClassName : implClassNames) {
try {
Object rawFramework = Class.forName(implClassName).newInstance();
if (rawFramework instanceof Framework)
framework = (Framework) rawFramework;
else
framework = new FrameworkWrapper((org.scalatools.testing.Framework) rawFramework);
break;
} catch (ClassNotFoundException e) {
logDebug(os, "Framework implementation '" + implClassName + "' not present.");
}
}
if (framework == null)
continue;
ArrayList<ForkTestDefinition> filteredTests = new ArrayList<ForkTestDefinition>();
for (Fingerprint testFingerprint : framework.tests()) {
for (Fingerprint testFingerprint : framework.fingerprints()) {
for (ForkTestDefinition test : tests) {
if (matches(testFingerprint, test.fingerprint)) filteredTests.add(test);
}
}
final org.scalatools.testing.Runner runner = framework.testRunner(getClass().getClassLoader(), loggers);
final Runner runner = framework.runner(frameworkArgs, remoteFrameworkArgs, getClass().getClassLoader());
for (ForkTestDefinition test : filteredTests)
runTestSafe(test, runner, framework, frameworkArgs, os);
runTestSafe(test, runner, loggers, os);
runner.done();
}
write(os, ForkTags.Done);
is.readObject();
}
void runTestSafe(ForkTestDefinition test, org.scalatools.testing.Runner runner, Framework framework, String[] frameworkArgs, ObjectOutputStream os) {
ForkEvent[] events;
class NestedTask {
private String parentName;
private Task task;
NestedTask(String parentName, Task task) {
this.parentName = parentName;
this.task = task;
}
public String getParentName() {
return parentName;
}
public Task getTask() {
return task;
}
}
void runTestSafe(ForkTestDefinition test, Runner runner, Logger[] loggers, ObjectOutputStream os) {
try {
events = runTest(test, runner, framework, frameworkArgs, os);
// TODO: To pass in correct explicitlySpecified and selectors
Task task = runner.task(test.name, test.fingerprint, false, new Selector[] { new SuiteSelector() });
List<NestedTask> nestedTasks = new ArrayList<NestedTask>();
for (Task nt : runTest(test, task, loggers, os))
nestedTasks.add(new NestedTask(test.name, nt));
while (true) {
List<NestedTask> newNestedTasks = new ArrayList<NestedTask>();
int nestedTasksLength = nestedTasks.size();
for (int i = 0; i < nestedTasksLength; i++) {
NestedTask nestedTask = nestedTasks.get(i);
String nestedParentName = nestedTask.getParentName() + "-" + i;
for (Task nt : runTest(new ForkTestDefinition(nestedParentName, test.fingerprint), nestedTask.getTask(), loggers, os)) {
newNestedTasks.add(new NestedTask(nestedParentName, nt));
}
}
if (newNestedTasks.size() == 0)
break;
else {
nestedTasks = newNestedTasks;
}
}
} catch (Throwable t) {
writeEvents(os, test, new ForkEvent[] { testError(os, test, "Uncaught exception when running " + test.name + ": " + t.toString(), t) });
}
}
Task[] runTest(ForkTestDefinition test, Task task, Logger[] loggers, ObjectOutputStream os) {
ForkEvent[] events;
Task[] nestedTasks;
try {
final List<ForkEvent> eventList = new ArrayList<ForkEvent>();
EventHandler handler = new EventHandler() { public void handle(Event e){ eventList.add(new ForkEvent(e)); } };
nestedTasks = task.execute(handler, loggers);
events = eventList.toArray(new ForkEvent[eventList.size()]);
}
catch (Throwable t) {
nestedTasks = new Task[0];
events = new ForkEvent[] { testError(os, test, "Uncaught exception when running " + test.name + ": " + t.toString(), t) };
}
writeEvents(os, test, events);
}
ForkEvent[] runTest(ForkTestDefinition test, org.scalatools.testing.Runner runner, Framework framework, String[] frameworkArgs, ObjectOutputStream os) {
final List<ForkEvent> events = new ArrayList<ForkEvent>();
EventHandler handler = new EventHandler() { public void handle(Event e){ events.add(new ForkEvent(e)); } };
if (runner instanceof Runner2) {
((Runner2) runner).run(test.name, test.fingerprint, handler, frameworkArgs);
} else if (test.fingerprint instanceof TestFingerprint) {
runner.run(test.name, (TestFingerprint) test.fingerprint, handler, frameworkArgs);
} else {
events.add(testError(os, test, "Framework '" + framework + "' does not support test '" + test.name + "'"));
}
return events.toArray(new ForkEvent[events.size()]);
return nestedTasks;
}
void run(ObjectInputStream is, ObjectOutputStream os) throws Exception {
try {
@ -198,22 +301,23 @@ public class ForkMain {
void internalError(Throwable t) {
System.err.println("Internal error when running tests: " + t.toString());
}
ForkEvent testEvent(final String name, final String desc, final Result r, final Throwable err) {
ForkEvent testEvent(final String fullyQualifiedName, final Fingerprint fingerprint, final Selector selector, final Status r, final Throwable err) {
return new ForkEvent(new Event() {
public String testName() { return name; }
public String description() { return desc; }
public Result result() { return r; }
public Throwable error() { return err; }
public String fullyQualifiedName() { return fullyQualifiedName; }
public boolean isModule() { return fingerprint instanceof SubclassFingerprint ? ((SubclassFingerprint) fingerprint).isModule() : ((AnnotatedFingerprint) fingerprint).isModule(); }
public Selector selector() { return selector; }
public Status status() { return r; }
public Throwable throwable() { return err; }
});
}
ForkEvent testError(ObjectOutputStream os, ForkTestDefinition test, String message) {
logError(os, message);
return testEvent(test.name, message, Result.Error, null);
return testEvent(test.name, test.fingerprint, new SuiteSelector(), Status.Error, null);
}
ForkEvent testError(ObjectOutputStream os, ForkTestDefinition test, String message, Throwable t) {
logError(os, message);
write(os, t);
return testEvent(test.name, message, Result.Error, t);
return testEvent(test.name, test.fingerprint, new SuiteSelector(), Status.Error, t);
}
}
}

View File

@ -0,0 +1,241 @@
package sbt;
import sbt.testing.*;
public class FrameworkWrapper implements Framework {
private org.scalatools.testing.Framework oldFramework;
public FrameworkWrapper(org.scalatools.testing.Framework oldFramework) {
this.oldFramework = oldFramework;
}
public String name() {
return oldFramework.name();
}
public Fingerprint[] fingerprints() {
org.scalatools.testing.Fingerprint[] oldFingerprints = oldFramework.tests();
int length = oldFingerprints.length;
Fingerprint[] fingerprints = new Fingerprint[length];
for (int i=0; i < length; i++) {
org.scalatools.testing.Fingerprint oldFingerprint = oldFingerprints[i];
if (oldFingerprint instanceof org.scalatools.testing.TestFingerprint)
fingerprints[i] = new TestFingerprintWrapper((org.scalatools.testing.TestFingerprint) oldFingerprint);
else if (oldFingerprint instanceof org.scalatools.testing.SubclassFingerprint)
fingerprints[i] = new SubclassFingerprintWrapper((org.scalatools.testing.SubclassFingerprint) oldFingerprint);
else
fingerprints[i] = new AnnotatedFingerprintWrapper((org.scalatools.testing.AnnotatedFingerprint) oldFingerprint);
}
return fingerprints;
}
public Runner runner(String[] args, String[] remoteArgs, ClassLoader testClassLoader) {
return new RunnerWrapper(oldFramework, testClassLoader, args);
}
}
class SubclassFingerprintWrapper implements SubclassFingerprint {
private String superclassName;
private boolean isModule;
private boolean requireNoArgConstructor;
public SubclassFingerprintWrapper(org.scalatools.testing.SubclassFingerprint oldFingerprint) {
this.superclassName = oldFingerprint.superClassName();
this.isModule = oldFingerprint.isModule();
this.requireNoArgConstructor = false; // Old framework SubclassFingerprint does not require no arg constructor
}
public boolean isModule() {
return isModule;
}
public String superclassName() {
return superclassName;
}
public boolean requireNoArgConstructor() {
return requireNoArgConstructor;
}
}
class AnnotatedFingerprintWrapper implements AnnotatedFingerprint {
private String annotationName;
private boolean isModule;
public AnnotatedFingerprintWrapper(org.scalatools.testing.AnnotatedFingerprint oldFingerprint) {
this.annotationName = oldFingerprint.annotationName();
this.isModule = oldFingerprint.isModule();
}
public boolean isModule() {
return isModule;
}
public String annotationName() {
return annotationName;
}
}
class TestFingerprintWrapper extends SubclassFingerprintWrapper {
public TestFingerprintWrapper(org.scalatools.testing.TestFingerprint oldFingerprint) {
super(oldFingerprint);
}
}
class EventHandlerWrapper implements org.scalatools.testing.EventHandler {
private EventHandler newEventHandler;
private String fullyQualifiedName;
private boolean isModule;
public EventHandlerWrapper(EventHandler newEventHandler, String fullyQualifiedName, boolean isModule) {
this.newEventHandler = newEventHandler;
this.fullyQualifiedName = fullyQualifiedName;
this.isModule = isModule;
}
public void handle(org.scalatools.testing.Event oldEvent) {
newEventHandler.handle(new EventWrapper(oldEvent, fullyQualifiedName, isModule));
}
}
class EventWrapper implements Event {
private org.scalatools.testing.Event oldEvent;
private String className;
private boolean classIsModule;
public EventWrapper(org.scalatools.testing.Event oldEvent, String className, boolean classIsModule) {
this.oldEvent = oldEvent;
this.className = className;
this.classIsModule = classIsModule;
}
public String fullyQualifiedName() {
return className;
}
public boolean isModule() {
return classIsModule;
}
public Selector selector() {
return new TestSelector(oldEvent.testName());
}
public Status status() {
switch (oldEvent.result()) {
case Success:
return Status.Success;
case Error:
return Status.Error;
case Failure:
return Status.Failure;
case Skipped:
return Status.Skipped;
default:
throw new IllegalStateException("Invalid status.");
}
}
public Throwable throwable() {
return oldEvent.error();
}
}
class RunnerWrapper implements Runner {
private org.scalatools.testing.Framework oldFramework;
private ClassLoader testClassLoader;
private String[] args;
public RunnerWrapper(org.scalatools.testing.Framework oldFramework, ClassLoader testClassLoader, String[] args) {
this.oldFramework = oldFramework;
this.testClassLoader = testClassLoader;
this.args = args;
}
public Task task(final String fullyQualifiedName, final Fingerprint fingerprint, boolean explicitlySpecified, Selector[] selectors) {
return new Task() {
public String[] tags() {
return new String[0]; // Old framework does not support tags
}
private org.scalatools.testing.Logger createOldLogger(final Logger logger) {
return new org.scalatools.testing.Logger() {
public boolean ansiCodesSupported() { return logger.ansiCodesSupported(); }
public void error(String msg) { logger.error(msg); }
public void warn(String msg) { logger.warn(msg); }
public void info(String msg) { logger.info(msg); }
public void debug(String msg) { logger.debug(msg); }
public void trace(Throwable t) { logger.trace(t); }
};
}
private void runRunner(org.scalatools.testing.Runner runner, Fingerprint fingerprint, EventHandler eventHandler) {
// Old runner only support subclass fingerprint.
final SubclassFingerprint subclassFingerprint = (SubclassFingerprint) fingerprint;
org.scalatools.testing.TestFingerprint oldFingerprint =
new org.scalatools.testing.TestFingerprint() {
public boolean isModule() { return subclassFingerprint.isModule(); }
public String superClassName() { return subclassFingerprint.superclassName(); }
};
runner.run(fullyQualifiedName, oldFingerprint, new EventHandlerWrapper(eventHandler, fullyQualifiedName, subclassFingerprint.isModule()), args);
}
private void runRunner2(org.scalatools.testing.Runner2 runner, Fingerprint fingerprint, EventHandler eventHandler) {
org.scalatools.testing.Fingerprint oldFingerprint = null;
boolean isModule = false;
if (fingerprint instanceof SubclassFingerprint) {
final SubclassFingerprint subclassFingerprint = (SubclassFingerprint) fingerprint;
oldFingerprint = new org.scalatools.testing.SubclassFingerprint() {
public boolean isModule() { return subclassFingerprint.isModule(); }
public String superClassName() { return subclassFingerprint.superclassName(); }
};
isModule = subclassFingerprint.isModule();
}
else {
final AnnotatedFingerprint annotatedFingerprint = (AnnotatedFingerprint) fingerprint;
oldFingerprint = new org.scalatools.testing.AnnotatedFingerprint() {
public boolean isModule() { return annotatedFingerprint.isModule(); }
public String annotationName() { return annotatedFingerprint.annotationName(); }
};
isModule = annotatedFingerprint.isModule();
}
runner.run(fullyQualifiedName, oldFingerprint, new EventHandlerWrapper(eventHandler, fullyQualifiedName, isModule), args);
}
public Task[] execute(EventHandler eventHandler, Logger[] loggers) {
int length = loggers.length;
org.scalatools.testing.Logger[] oldLoggers = new org.scalatools.testing.Logger[length];
for (int i=0; i<length; i++) {
oldLoggers[i] = createOldLogger(loggers[i]);
}
org.scalatools.testing.Runner runner = oldFramework.testRunner(testClassLoader, oldLoggers);
if (runner instanceof org.scalatools.testing.Runner2) {
runRunner2((org.scalatools.testing.Runner2) runner, fingerprint, eventHandler);
}
else {
runRunner(runner, fingerprint, eventHandler);
}
return new Task[0];
}
};
}
public String done() {
return "";
}
public String[] args() {
return args;
}
public String[] remoteArgs() {
return new String[0]; // Old framework does not support remoteArgs
}
}

View File

@ -18,7 +18,7 @@ package sbt
*/
import scala.util.parsing.combinator._
import org.scalatools.testing.{Fingerprint, AnnotatedFingerprint, TestFingerprint}
import testing.{Fingerprint, AnnotatedFingerprint, TestFingerprint}
import DiscoveredParser._
sealed abstract class Discovered extends Fingerprint with NotNull

View File

@ -5,9 +5,10 @@ package sbt
import java.io.File
import java.net.URLClassLoader
import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, SubclassFingerprint, TestFingerprint}
import org.scalatools.testing.{Event, EventHandler, Framework, Runner, Runner2, Logger=>TLogger}
import testing.{Logger=>TLogger, Task => TestTask, _}
import org.scalatools.testing.{Framework => OldFramework}
import classpath.{ClasspathUtilities, DualLoader, FilteredLoader}
import scala.annotation.tailrec
object TestResult extends Enumeration
{
@ -17,19 +18,38 @@ object TestResult extends Enumeration
object TestFrameworks
{
val ScalaCheck = new TestFramework("org.scalacheck.ScalaCheckFramework")
val ScalaTest = new TestFramework("org.scalatest.tools.ScalaTestFramework")
val ScalaTest = new TestFramework("org.scalatest.tools.Framework", "org.scalatest.tools.ScalaTestFramework")
val Specs = new TestFramework("org.specs.runner.SpecsFramework")
val Specs2 = new TestFramework("org.specs2.runner.SpecsFramework")
val JUnit = new TestFramework("com.novocode.junit.JUnitFramework")
}
case class TestFramework(val implClassName: String)
case class TestFramework(val implClassNames: String*)
{
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 }
@tailrec
private def createFramework(loader: ClassLoader, log: Logger, frameworkClassNames: List[String]): Option[Framework] = {
frameworkClassNames match {
case head :: tail =>
try
{
Some(Class.forName(head, true, loader).newInstance match {
case newFramework: Framework => newFramework
case oldFramework: OldFramework => new FrameworkWrapper(oldFramework)
})
}
catch
{
case e: ClassNotFoundException =>
log.debug("Framework implementation '" + head + "' not present.");
createFramework(loader, log, tail)
}
case Nil =>
None
}
}
def create(loader: ClassLoader, log: Logger): Option[Framework] =
createFramework(loader, log, implClassNames.toList)
}
final class TestDefinition(val name: String, val fingerprint: Fingerprint)
{
@ -43,50 +63,41 @@ final class TestDefinition(val name: String, val fingerprint: Fingerprint)
override def hashCode: Int = (name.hashCode, TestFramework.hashCode(fingerprint)).hashCode
}
final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq[TestReportListener], log: Logger)
{
private[this] def run(testDefinition: TestDefinition, handler: EventHandler, args: Array[String]): Unit =
{
val loggers = listeners.flatMap(_.contentLogger(testDefinition))
val delegate = framework.testRunner(loader, loggers.map(_.log).toArray)
try { delegateRun(delegate, testDefinition, handler, args) }
finally { loggers.foreach( _.flush() ) }
}
private[this] def delegateRun(delegate: Runner, 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 _ => sys.error("Framework '" + framework + "' does not support test '" + testDefinition + "'")
}
final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log: Logger) {
final def run(testDefinition: TestDefinition, args: Seq[String]): TestResult.Value =
final def task(testDefinition: TestDefinition): TestTask =
delegate.task(testDefinition.name, testDefinition.fingerprint, false, Array(new SuiteSelector)) // TODO: To pass in correct explicitlySpecified and selectors
final def run(testDefinition: TestDefinition, testTask: TestTask): (SuiteResult, Seq[TestTask]) =
{
log.debug("Running " + testDefinition + " with arguments " + args.mkString(", "))
log.debug("Running " + testDefinition)
val name = testDefinition.name
def runTest() =
{
// 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 } }
run(testDefinition, handler, args.toArray)
val loggers = listeners.flatMap(_.contentLogger(testDefinition))
val nestedTasks =
try testTask.execute(handler, loggers.map(_.log).toArray)
finally loggers.foreach( _.flush() )
val event = TestEvent(results)
safeListenersCall(_.testEvent( event ))
event.result
(SuiteResult(results), nestedTasks.toSeq)
}
safeListenersCall(_.startGroup(name))
try
{
val result = runTest().getOrElse(TestResult.Passed)
safeListenersCall(_.endGroup(name, result))
result
val (suiteResult, nestedTasks) = runTest()
safeListenersCall(_.endGroup(name, suiteResult.result))
(suiteResult, nestedTasks)
}
catch
{
case e: Throwable =>
safeListenersCall(_.endGroup(name, e))
TestResult.Error
(SuiteResult.Error, Seq.empty[TestTask])
}
}
@ -95,13 +106,12 @@ final class TestRunner(framework: Framework, loader: ClassLoader, listeners: Seq
}
object TestFramework
{
def getTests(framework: Framework): Seq[Fingerprint] =
framework.getClass.getMethod("tests").invoke(framework) match
{
def getFingerprints(framework: Framework): Seq[Fingerprint] =
framework.getClass.getMethod("fingerprints").invoke(framework) match
{
case newStyle: Array[Fingerprint] => newStyle.toList
case oldStyle: Array[TestFingerprint] => oldStyle.toList
case _ => sys.error("Could not call 'tests' on framework " + framework)
case fingerprints: Array[Fingerprint] => fingerprints.toList
case _ => sys.error("Could not call 'fingerprints' on framework " + framework)
}
private val ScalaCompilerJarPackages = "scala.tools." :: "jline." :: "ch.epfl.lamp." :: Nil
@ -113,42 +123,43 @@ object TestFramework
it.foreach(i => try f(i) catch { case e: Exception => log.trace(e); log.error(e.toString) })
private[sbt] def hashCode(f: Fingerprint): Int = f match {
case s: SubclassFingerprint => (s.isModule, s.superClassName).hashCode
case s: SubclassFingerprint => (s.isModule, s.superclassName).hashCode
case a: AnnotatedFingerprint => (a.isModule, a.annotationName).hashCode
case _ => 0
}
def matches(a: Fingerprint, b: Fingerprint) =
(a, b) match
{
case (a: SubclassFingerprint, b: SubclassFingerprint) => a.isModule == b.isModule && a.superClassName == b.superClassName
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
}
def toString(f: Fingerprint): String =
f match
{
case sf: SubclassFingerprint => "subclass(" + sf.isModule + ", " + sf.superClassName + ")"
case sf: SubclassFingerprint => "subclass(" + sf.isModule + ", " + sf.superclassName + ")"
case af: AnnotatedFingerprint => "annotation(" + af.isModule + ", " + af.annotationName + ")"
case _ => f.toString
}
def testTasks(frameworks: Seq[Framework],
def testTasks(frameworks: Map[TestFramework, Framework],
runners: Map[TestFramework, Runner],
testLoader: ClassLoader,
tests: Seq[TestDefinition],
log: Logger,
listeners: Seq[TestReportListener],
testArgsByFramework: Map[Framework, Seq[String]]):
(() => Unit, Seq[(String, () => TestResult.Value)], TestResult.Value => () => Unit) =
(() => Unit, Seq[(String, TestFunction)], TestResult.Value => () => Unit) =
{
val arguments = testArgsByFramework withDefaultValue Nil
val mappedTests = testMap(frameworks, tests, arguments)
val mappedTests = testMap(frameworks.values.toSeq, tests, arguments)
if(mappedTests.isEmpty)
(() => (), Nil, _ => () => () )
else
createTestTasks(testLoader, mappedTests, tests, log, listeners)
createTestTasks(testLoader, runners.map { case (tf, r) => (frameworks(tf), new TestRunner(r, listeners, log))}, mappedTests, tests, log, listeners)
}
private[this] def order(mapped: Map[String, () => TestResult.Value], inputs: Seq[TestDefinition]): Seq[(String, () => TestResult.Value)] =
private[this] def order(mapped: Map[String, TestFunction], inputs: Seq[TestDefinition]): Seq[(String, TestFunction)] =
for( d <- inputs; act <- mapped.get(d.name) ) yield (d.name, act)
private[this] def testMap(frameworks: Seq[Framework], tests: Seq[TestDefinition], args: Map[Framework, Seq[String]]):
@ -160,7 +171,7 @@ object TestFramework
{
for(test <- tests if !map.values.exists(_.contains(test)))
{
def isTestForFramework(framework: Framework) = getTests(framework).exists {t => matches(t, test.fingerprint) }
def isTestForFramework(framework: Framework) = getFingerprints(framework).exists {t => matches(t, test.fingerprint) }
for(framework <- frameworks.find(isTestForFramework))
map.getOrElseUpdate(framework, new HashSet[TestDefinition]) += test
}
@ -171,7 +182,7 @@ object TestFramework
}
private[this] def mergeDuplicates(framework: Framework, tests: Seq[TestDefinition]): Set[TestDefinition] =
{
val frameworkPrints = framework.tests.reverse
val frameworkPrints = framework.fingerprints.reverse
def pickOne(prints: Seq[Fingerprint]): Fingerprint =
frameworkPrints.find(prints.toSet) getOrElse prints.head
val uniqueDefs =
@ -179,24 +190,25 @@ object TestFramework
new TestDefinition(name, pickOne(defs.map(_.fingerprint)))
uniqueDefs.toSet
}
private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], ordered: Seq[TestDefinition], log: Logger, listeners: Seq[TestReportListener]) =
private def createTestTasks(loader: ClassLoader, runners: Map[Framework, TestRunner], tests: Map[Framework, (Set[TestDefinition], Seq[String])], ordered: Seq[TestDefinition], log: Logger, listeners: Seq[TestReportListener]) =
{
val testsListeners = listeners collect { case tl: TestsListener => tl }
def foreachListenerSafe(f: TestsListener => Unit): () => Unit = () => safeForeach(testsListeners, log)(f)
import TestResult.{Error,Passed,Failed}
import TestResult.{Error,Passed,Failed}
val startTask = foreachListenerSafe(_.doInit)
val testTasks =
tests flatMap { case (framework, (testDefinitions, testArgs)) =>
val runner = new TestRunner(framework, loader, listeners, log)
for(testDefinition <- testDefinitions) yield
{
val runTest = () => withContextLoader(loader) { runner.run(testDefinition, testArgs) }
(testDefinition.name, runTest)
}
val runner = runners(framework)
for(testDefinition <- testDefinitions) yield
{
val testTask = withContextLoader(loader) { runner.task(testDefinition) }
val testFunction = createTestFunction(loader, testDefinition, runner, testTask)
(testDefinition.name, testFunction)
}
}
val endTask = (result: TestResult.Value) => foreachListenerSafe(_.doComplete(result))
@ -210,11 +222,20 @@ object TestFramework
}
def createTestLoader(classpath: Seq[File], scalaInstance: ScalaInstance, tempDir: File): ClassLoader =
{
val interfaceJar = IO.classLocationFile(classOf[org.scalatools.testing.Framework])
val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.")
val interfaceJar = IO.classLocationFile(classOf[testing.Framework])
val interfaceFilter = (name: String) => name.startsWith("org.scalatools.testing.") || name.startsWith("sbt.testing.")
val notInterfaceFilter = (name: String) => !interfaceFilter(name)
val dual = new DualLoader(scalaInstance.loader, notInterfaceFilter, x => true, getClass.getClassLoader, interfaceFilter, x => false)
val main = ClasspathUtilities.makeLoader(classpath, dual, scalaInstance, tempDir)
ClasspathUtilities.filterByClasspath(interfaceJar +: classpath, main)
}
def createTestFunction(loader: ClassLoader, testDefinition: TestDefinition, runner:TestRunner, testTask: TestTask): TestFunction =
new TestFunction(testDefinition, runner, (r: TestRunner) => withContextLoader(loader) { r.run(testDefinition, testTask) }) { def tags = testTask.tags }
}
abstract class TestFunction(val testDefinition: TestDefinition, val runner: TestRunner, fun: (TestRunner) => (SuiteResult, Seq[TestTask])) {
def apply(): (SuiteResult, Seq[TestTask]) = fun(runner)
def tags: Seq[String]
}

View File

@ -4,7 +4,7 @@
package sbt
import org.scalatools.testing.{Logger => TLogger, Event => TEvent, Result => TResult}
import testing.{Logger => TLogger, Event => TEvent, Status => TStatus}
trait TestReportListener
{
@ -28,6 +28,24 @@ trait TestsListener extends TestReportListener
def doComplete(finalResult: TestResult.Value)
}
final class SuiteResult(val result: TestResult.Value, val passedCount: Int, val failureCount: Int, val errorCount: Int, val skippedCount: Int)
object SuiteResult
{
def apply(events: Seq[TEvent]): SuiteResult =
{
def count(status: TStatus) = events.count(_.status == status)
val overallResult = (TestResult.Passed /: events) { (sum, event) =>
val status = event.status
if(sum == TestResult.Error || status == TStatus.Error) TestResult.Error
else if(sum == TestResult.Failed || status == TStatus.Failure) TestResult.Failed
else TestResult.Passed
}
new SuiteResult (overallResult, count(TStatus.Success), count(TStatus.Failure), count(TStatus.Error), count(TStatus.Skipped))
}
val Error: SuiteResult = new SuiteResult(TestResult.Error, 0, 0, 0, 0)
val Empty: SuiteResult = new SuiteResult(TestResult.Passed, 0, 0, 0, 0)
}
abstract class TestEvent extends NotNull
{
def result: Option[TestResult.Value]
@ -38,9 +56,9 @@ object TestEvent
def apply(events: Seq[TEvent]): TestEvent =
{
val overallResult = (TestResult.Passed /: events) { (sum, event) =>
val result = event.result
if(sum == TestResult.Error || result == TResult.Error) TestResult.Error
else if(sum == TestResult.Failed || result == TResult.Failure) TestResult.Failed
val status = event.status
if(sum == TestResult.Error || status == TStatus.Error) TestResult.Error
else if(sum == TestResult.Failed || status == TStatus.Failure) TestResult.Failed
else TestResult.Passed
}
new TestEvent {
@ -77,43 +95,18 @@ final class TestLogging(val global: TLogger, val logTest: TestDefinition => Cont
final class ContentLogger(val log: TLogger, val flush: () => Unit)
class TestLogger(val logging: TestLogging) extends TestsListener
{
import logging.{global => log, logTest}
import java.util.concurrent.atomic.AtomicInteger
protected val skippedCount, errorsCount, passedCount, failuresCount = new AtomicInteger
import logging.{global => log, logTest}
def startGroup(name: String) {}
def testEvent(event: TestEvent): Unit = event.detail.foreach(count)
def testEvent(event: TestEvent): Unit = {}
def endGroup(name: String, t: Throwable)
{
log.trace(t)
log.error("Could not run test " + name + ": " + t.toString)
}
def endGroup(name: String, result: TestResult.Value) {}
protected def count(event: TEvent): Unit =
{
val count = event.result match {
case TResult.Error => errorsCount
case TResult.Success => passedCount
case TResult.Failure => failuresCount
case TResult.Skipped => skippedCount
}
count.incrementAndGet()
}
def doInit
{
for (count <- List(skippedCount, errorsCount, passedCount, failuresCount)) count.set(0)
}
/** called once, at end. */
def doComplete(finalResult: TestResult.Value): Unit =
{
val (skipped, errors, passed, failures) = (skippedCount.get, errorsCount.get, passedCount.get, failuresCount.get)
val totalCount = failures + errors + skipped + passed
val postfix = ": Total " + totalCount + ", Failed " + failures + ", Errors " + errors + ", Passed " + passed + ", Skipped " + skipped
finalResult match {
case TestResult.Error => log.error("Error" + postfix)
case TestResult.Passed => log.info("Passed: " + postfix)
case TestResult.Failed => log.error("Failed: " + postfix)
}
}
def doInit {}
/** called once, at end of test group. */
def doComplete(finalResult: TestResult.Value): Unit = {}
override def contentLogger(test: TestDefinition): Option[ContentLogger] = Some(logTest(test))
}

View File

@ -1,20 +1,20 @@
package sbt
sealed trait SourcePosition
sealed trait FilePosition extends SourcePosition {
def path: String
def startLine: Int
}
case object NoPosition extends SourcePosition
final case class LinePosition(path: String, startLine: Int) extends FilePosition
final case class LineRange(start: Int, end: Int) {
def shift(n: Int) = new LineRange(start + n, end + n)
}
final case class RangePosition(path: String, range: LineRange) extends FilePosition {
def startLine = range.start
}
package sbt
sealed trait SourcePosition
sealed trait FilePosition extends SourcePosition {
def path: String
def startLine: Int
}
case object NoPosition extends SourcePosition
final case class LinePosition(path: String, startLine: Int) extends FilePosition
final case class LineRange(start: Int, end: Int) {
def shift(n: Int) = new LineRange(start + n, end + n)
}
final case class RangePosition(path: String, range: LineRange) extends FilePosition {
def startLine = range.start
}