diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a5d9c6403 --- /dev/null +++ b/.gitattributes @@ -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 diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index 3be3ad60d..d4421e713 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -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() } } diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index c6549e4e2..4a7eda96a 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -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 => } } diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 3cddfd047..c5aa1fd3b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -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] = diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index 3cdbf4395..fa0e04ec9 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -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._ diff --git a/project/Sbt.scala b/project/Sbt.scala index 9fdf2dd2c..a67935593 100644 --- a/project/Sbt.scala +++ b/project/Sbt.scala @@ -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 diff --git a/sbt/src/sbt-test/actions/reload/test b/sbt/src/sbt-test/actions/reload/test index ef1be05a7..69e42a70f 100755 --- a/sbt/src/sbt-test/actions/reload/test +++ b/sbt/src/sbt-test/actions/reload/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/api/basic/src/main/scala/testCase/JFoo.java b/sbt/src/sbt-test/api/basic/src/main/scala/testCase/JFoo.java index ec3f0e35d..e849d8df7 100755 --- a/sbt/src/sbt-test/api/basic/src/main/scala/testCase/JFoo.java +++ b/sbt/src/sbt-test/api/basic/src/main/scala/testCase/JFoo.java @@ -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 { + +} diff --git a/sbt/src/sbt-test/api/basic/src/main/scala/testCase/annotations.scala b/sbt/src/sbt-test/api/basic/src/main/scala/testCase/annotations.scala index dcce55d75..c74c28494 100644 --- a/sbt/src/sbt-test/api/basic/src/main/scala/testCase/annotations.scala +++ b/sbt/src/sbt-test/api/basic/src/main/scala/testCase/annotations.scala @@ -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; +} diff --git a/sbt/src/sbt-test/dependency-management/force/project/TestProject.scala b/sbt/src/sbt-test/dependency-management/force/project/TestProject.scala index d0d13403c..8a6bc5d1a 100755 --- a/sbt/src/sbt-test/dependency-management/force/project/TestProject.scala +++ b/sbt/src/sbt-test/dependency-management/force/project/TestProject.scala @@ -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.") + } +} diff --git a/sbt/src/sbt-test/dependency-management/force/test b/sbt/src/sbt-test/dependency-management/force/test index 314b98fd7..2cb44cea7 100755 --- a/sbt/src/sbt-test/dependency-management/force/test +++ b/sbt/src/sbt-test/dependency-management/force/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/project/session-save/build.check.1 b/sbt/src/sbt-test/project/session-save/build.check.1 index 1e25d0391..f1dd6431d 100755 --- a/sbt/src/sbt-test/project/session-save/build.check.1 +++ b/sbt/src/sbt-test/project/session-save/build.check.1 @@ -1,4 +1,4 @@ -k1 := {error("k1")} - -k2 <<= k1 map identity - +k1 := {error("k1")} + +k2 <<= k1 map identity + diff --git a/sbt/src/sbt-test/project/session-save/build.check.2 b/sbt/src/sbt-test/project/session-save/build.check.2 index 42c414288..2bb4dc49d 100755 --- a/sbt/src/sbt-test/project/session-save/build.check.2 +++ b/sbt/src/sbt-test/project/session-save/build.check.2 @@ -1,4 +1,4 @@ -k1 := {} - -k2 <<= k1 map identity - +k1 := {} + +k2 <<= k1 map identity + diff --git a/sbt/src/sbt-test/project/session-save/build.check.3 b/sbt/src/sbt-test/project/session-save/build.check.3 index ad18cca9f..0c0b932c5 100755 --- a/sbt/src/sbt-test/project/session-save/build.check.3 +++ b/sbt/src/sbt-test/project/session-save/build.check.3 @@ -1,6 +1,6 @@ -k1 := {} - -k2 := {} - -k1 <<= k1 map {_ => error("k1")} - +k1 := {} + +k2 := {} + +k1 <<= k1 map {_ => error("k1")} + diff --git a/sbt/src/sbt-test/project/session-save/build.sbt b/sbt/src/sbt-test/project/session-save/build.sbt index fcf95068c..d0208fa47 100755 --- a/sbt/src/sbt-test/project/session-save/build.sbt +++ b/sbt/src/sbt-test/project/session-save/build.sbt @@ -1,6 +1,6 @@ -k1 := { -} - -k2 := { -} - +k1 := { +} + +k2 := { +} + diff --git a/sbt/src/sbt-test/project/session-save/project/Build.scala b/sbt/src/sbt-test/project/session-save/project/Build.scala index d90a26779..b7ba35d2f 100755 --- a/sbt/src/sbt-test/project/session-save/project/Build.scala +++ b/sbt/src/sbt-test/project/session-save/project/Build.scala @@ -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(".")) +} diff --git a/sbt/src/sbt-test/project/session-save/test b/sbt/src/sbt-test/project/session-save/test index c552f3a31..58f8a7b46 100755 --- a/sbt/src/sbt-test/project/session-save/test +++ b/sbt/src/sbt-test/project/session-save/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/do-not-discover/build.sbt b/sbt/src/sbt-test/tests/do-not-discover/build.sbt new file mode 100644 index 000000000..2e8f50f5b --- /dev/null +++ b/sbt/src/sbt-test/tests/do-not-discover/build.sbt @@ -0,0 +1,5 @@ +scalaVersion := "2.10.1" + +libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15" + +testOptions in Test += Tests.Argument("-r", "custom.CustomReporter") \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/do-not-discover/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/do-not-discover/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..f21595450 --- /dev/null +++ b/sbt/src/sbt-test/tests/do-not-discover/src/main/scala/custom/CustomReporter.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec.scala b/sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec.scala new file mode 100644 index 000000000..57ec51bdf --- /dev/null +++ b/sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec2.scala b/sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec2.scala new file mode 100644 index 000000000..b3e2f6bd2 --- /dev/null +++ b/sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec2.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/do-not-discover/test b/sbt/src/sbt-test/tests/do-not-discover/test new file mode 100644 index 000000000..82b725387 --- /dev/null +++ b/sbt/src/sbt-test/tests/do-not-discover/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/done/build.sbt b/sbt/src/sbt-test/tests/done/build.sbt new file mode 100644 index 000000000..2e8f50f5b --- /dev/null +++ b/sbt/src/sbt-test/tests/done/build.sbt @@ -0,0 +1,5 @@ +scalaVersion := "2.10.1" + +libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15" + +testOptions in Test += Tests.Argument("-r", "custom.CustomReporter") \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/done/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/done/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..a4d4b7eb9 --- /dev/null +++ b/sbt/src/sbt-test/tests/done/src/main/scala/custom/CustomReporter.scala @@ -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") + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec.scala b/sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec.scala new file mode 100644 index 000000000..57ec51bdf --- /dev/null +++ b/sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec2.scala b/sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec2.scala new file mode 100644 index 000000000..ce747f260 --- /dev/null +++ b/sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec2.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/done/test b/sbt/src/sbt-test/tests/done/test new file mode 100644 index 000000000..2307aed49 --- /dev/null +++ b/sbt/src/sbt-test/tests/done/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-par/build.sbt b/sbt/src/sbt-test/tests/nested-inproc-par/build.sbt new file mode 100644 index 000000000..0895fe82a --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-par/build.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-par/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/nested-inproc-par/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..f21595450 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-par/src/main/scala/custom/CustomReporter.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-par/src/test/scala/com/test/NestedSpecs.scala b/sbt/src/sbt-test/tests/nested-inproc-par/src/test/scala/com/test/NestedSpecs.scala new file mode 100644 index 000000000..b9d5a838f --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-par/src/test/scala/com/test/NestedSpecs.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-par/test b/sbt/src/sbt-test/tests/nested-inproc-par/test new file mode 100644 index 000000000..dedaa5c47 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-par/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt b/sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt new file mode 100644 index 000000000..dda0ba5dc --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..f21595450 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala b/sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala new file mode 100644 index 000000000..b9d5a838f --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-inproc-seq/test b/sbt/src/sbt-test/tests/nested-inproc-seq/test new file mode 100644 index 000000000..6481f9187 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-inproc-seq/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-subproc/build.sbt b/sbt/src/sbt-test/tests/nested-subproc/build.sbt new file mode 100644 index 000000000..f2336a77e --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-subproc/build.sbt @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-subproc/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/nested-subproc/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..f21595450 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-subproc/src/main/scala/custom/CustomReporter.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-subproc/src/test/scala/com/test/NestedSpecs.scala b/sbt/src/sbt-test/tests/nested-subproc/src/test/scala/com/test/NestedSpecs.scala new file mode 100644 index 000000000..b9d5a838f --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-subproc/src/test/scala/com/test/NestedSpecs.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/nested-subproc/test b/sbt/src/sbt-test/tests/nested-subproc/test new file mode 100644 index 000000000..faf8391a9 --- /dev/null +++ b/sbt/src/sbt-test/tests/nested-subproc/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/single-runner/build.sbt b/sbt/src/sbt-test/tests/single-runner/build.sbt new file mode 100644 index 000000000..2e8f50f5b --- /dev/null +++ b/sbt/src/sbt-test/tests/single-runner/build.sbt @@ -0,0 +1,5 @@ +scalaVersion := "2.10.1" + +libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15" + +testOptions in Test += Tests.Argument("-r", "custom.CustomReporter") \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/single-runner/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/single-runner/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..39c7085ac --- /dev/null +++ b/sbt/src/sbt-test/tests/single-runner/src/main/scala/custom/CustomReporter.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec.scala b/sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec.scala new file mode 100644 index 000000000..57ec51bdf --- /dev/null +++ b/sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec2.scala b/sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec2.scala new file mode 100644 index 000000000..ce747f260 --- /dev/null +++ b/sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec2.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/single-runner/test b/sbt/src/sbt-test/tests/single-runner/test new file mode 100644 index 000000000..c7c5eb165 --- /dev/null +++ b/sbt/src/sbt-test/tests/single-runner/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/t543/project/Ticket543Test.scala b/sbt/src/sbt-test/tests/t543/project/Ticket543Test.scala index 0ccfa8381..ba9881ba0 100755 --- a/sbt/src/sbt-test/tests/t543/project/Ticket543Test.scala +++ b/sbt/src/sbt-test/tests/t543/project/Ticket543Test.scala @@ -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() } diff --git a/sbt/src/sbt-test/tests/task/build.sbt b/sbt/src/sbt-test/tests/task/build.sbt new file mode 100644 index 000000000..2e8f50f5b --- /dev/null +++ b/sbt/src/sbt-test/tests/task/build.sbt @@ -0,0 +1,5 @@ +scalaVersion := "2.10.1" + +libraryDependencies += "org.scalatest" %% "scalatest" % "2.0.M6-SNAP15" + +testOptions in Test += Tests.Argument("-r", "custom.CustomReporter") \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/task/src/main/scala/custom/CustomReporter.scala b/sbt/src/sbt-test/tests/task/src/main/scala/custom/CustomReporter.scala new file mode 100644 index 000000000..f21595450 --- /dev/null +++ b/sbt/src/sbt-test/tests/task/src/main/scala/custom/CustomReporter.scala @@ -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 _ => + } + } +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec.scala b/sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec.scala new file mode 100644 index 000000000..57ec51bdf --- /dev/null +++ b/sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec2.scala b/sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec2.scala new file mode 100644 index 000000000..ce747f260 --- /dev/null +++ b/sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec2.scala @@ -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 ` {} +} \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/task/test b/sbt/src/sbt-test/tests/task/test new file mode 100644 index 000000000..9c84af088 --- /dev/null +++ b/sbt/src/sbt-test/tests/task/test @@ -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 \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/test-quick/changed/A.scala b/sbt/src/sbt-test/tests/test-quick/changed/A.scala index 869db8f1c..6eecd055c 100755 --- a/sbt/src/sbt-test/tests/test-quick/changed/A.scala +++ b/sbt/src/sbt-test/tests/test-quick/changed/A.scala @@ -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. } \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/test-quick/changed/B.scala b/sbt/src/sbt-test/tests/test-quick/changed/B.scala index 0fcd03a98..e81be16d9 100755 --- a/sbt/src/sbt-test/tests/test-quick/changed/B.scala +++ b/sbt/src/sbt-test/tests/test-quick/changed/B.scala @@ -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 +} diff --git a/sbt/src/sbt-test/tests/test-quick/changed/Base.scala b/sbt/src/sbt-test/tests/test-quick/changed/Base.scala index c569ca19e..492839a88 100755 --- a/sbt/src/sbt-test/tests/test-quick/changed/Base.scala +++ b/sbt/src/sbt-test/tests/test-quick/changed/Base.scala @@ -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 = "" +} diff --git a/sbt/src/sbt-test/tests/test-quick/src/main/scala/A.scala b/sbt/src/sbt-test/tests/test-quick/src/main/scala/A.scala index 6e5bc52de..9b76dea09 100755 --- a/sbt/src/sbt-test/tests/test-quick/src/main/scala/A.scala +++ b/sbt/src/sbt-test/tests/test-quick/src/main/scala/A.scala @@ -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 } \ No newline at end of file diff --git a/sbt/src/sbt-test/tests/test-quick/src/main/scala/B.scala b/sbt/src/sbt-test/tests/test-quick/src/main/scala/B.scala index 43be25453..4f4c91fcf 100755 --- a/sbt/src/sbt-test/tests/test-quick/src/main/scala/B.scala +++ b/sbt/src/sbt-test/tests/test-quick/src/main/scala/B.scala @@ -1,4 +1,4 @@ -class B { - def foo = 1 - def bar(a: A) {} -} +class B { + def foo = 1 + def bar(a: A) {} +} diff --git a/sbt/src/sbt-test/tests/test-quick/src/test/scala/Base.scala b/sbt/src/sbt-test/tests/test-quick/src/test/scala/Base.scala index 5604f26bd..e32283600 100755 --- a/sbt/src/sbt-test/tests/test-quick/src/test/scala/Base.scala +++ b/sbt/src/sbt-test/tests/test-quick/src/test/scala/Base.scala @@ -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") +} diff --git a/sbt/src/sbt-test/tests/test-quick/src/test/scala/Create.scala b/sbt/src/sbt-test/tests/test-quick/src/test/scala/Create.scala index 24dc57ea2..121de95b0 100755 --- a/sbt/src/sbt-test/tests/test-quick/src/test/scala/Create.scala +++ b/sbt/src/sbt-test/tests/test-quick/src/test/scala/Create.scala @@ -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) + } + +} diff --git a/sbt/src/sbt-test/tests/test-quick/src/test/scala/Delete.scala b/sbt/src/sbt-test/tests/test-quick/src/test/scala/Delete.scala index 6bd113901..cd2eb6617 100755 --- a/sbt/src/sbt-test/tests/test-quick/src/test/scala/Delete.scala +++ b/sbt/src/sbt-test/tests/test-quick/src/test/scala/Delete.scala @@ -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() + } + +} diff --git a/sbt/src/sbt-test/tests/test-quick/test b/sbt/src/sbt-test/tests/test-quick/test index dec97baa0..865e74a5a 100755 --- a/sbt/src/sbt-test/tests/test-quick/test +++ b/sbt/src/sbt-test/tests/test-quick/test @@ -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 \ No newline at end of file diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 11a03927f..8ec30b795 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -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 filteredTests = new ArrayList(); - 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 nestedTasks = new ArrayList(); + for (Task nt : runTest(test, task, loggers, os)) + nestedTasks.add(new NestedTask(test.name, nt)); + while (true) { + List newNestedTasks = new ArrayList(); + 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 eventList = new ArrayList(); + 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 events = new ArrayList(); - 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); } } } diff --git a/testing/agent/src/main/java/sbt/FrameworkWrapper.java b/testing/agent/src/main/java/sbt/FrameworkWrapper.java new file mode 100644 index 000000000..55d978025 --- /dev/null +++ b/testing/agent/src/main/java/sbt/FrameworkWrapper.java @@ -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; iTLogger} + 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] } diff --git a/testing/src/main/scala/sbt/TestReportListener.scala b/testing/src/main/scala/sbt/TestReportListener.scala index d20dbbb76..9cb4e66c5 100644 --- a/testing/src/main/scala/sbt/TestReportListener.scala +++ b/testing/src/main/scala/sbt/TestReportListener.scala @@ -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)) } diff --git a/util/collection/src/main/scala/sbt/Positions.scala b/util/collection/src/main/scala/sbt/Positions.scala index b2aa22ee2..f52c583b0 100755 --- a/util/collection/src/main/scala/sbt/Positions.scala +++ b/util/collection/src/main/scala/sbt/Positions.scala @@ -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 +}