From 838b737caad40f19983641be7c41710445f7b01a Mon Sep 17 00:00:00 2001 From: cheeseng Date: Mon, 1 Apr 2013 11:54:15 +0800 Subject: [PATCH 01/16] Bump up test-interface dependency to version 1.0-SNAP3. --- project/Sbt.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 From 18bc8423b58f0463b0787e76fc263ac1b1ac1a32 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 3 Apr 2013 21:58:54 +0800 Subject: [PATCH 02/16] Initial version that uses new framework API in test-interface 1.0: -Changed usages and implementations of interfaces in org.scalatools.testing._ to use/implement interfaces/classes in sbt.testing._ instead. -Added sbt.testing to interfaceFilter in TestFramework.createTestLoader method to enable loading of classes in sbt.testing package. -Added FrameworkWrapper.java to wrap old framework implementations. -Added code in ForkMain.java to serialize Selectors. --- .../src/main/scala/sbt/ForkTests.scala | 2 +- main/actions/src/main/scala/sbt/Tests.scala | 6 +- main/src/main/scala/sbt/Defaults.scala | 2 +- main/src/main/scala/sbt/Keys.scala | 2 +- .../sbt-test/tests/do-not-discover/build.sbt | 5 + .../main/scala/custom/CustomReporter.scala | 30 +++ .../src/test/scala/com/test/TestSpec.scala | 12 + .../src/test/scala/com/test/TestSpec2.scala | 13 + sbt/src/sbt-test/tests/do-not-discover/test | 72 ++++++ sbt/src/sbt-test/tests/task/build.sbt | 5 + .../main/scala/custom/CustomReporter.scala | 30 +++ .../src/test/scala/com/test/TestSpec.scala | 12 + .../src/test/scala/com/test/TestSpec2.scala | 12 + sbt/src/sbt-test/tests/task/test | 72 ++++++ testing/agent/src/main/java/sbt/ForkMain.java | 131 +++++++--- .../src/main/java/sbt/FrameworkWrapper.java | 241 ++++++++++++++++++ .../impl/src/main/scala/sbt/TestParser.scala | 2 +- .../src/main/scala/sbt/TestFramework.scala | 79 +++--- .../main/scala/sbt/TestReportListener.scala | 18 +- 19 files changed, 651 insertions(+), 95 deletions(-) create mode 100644 sbt/src/sbt-test/tests/do-not-discover/build.sbt create mode 100644 sbt/src/sbt-test/tests/do-not-discover/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec.scala create mode 100644 sbt/src/sbt-test/tests/do-not-discover/src/test/scala/com/test/TestSpec2.scala create mode 100644 sbt/src/sbt-test/tests/do-not-discover/test create mode 100644 sbt/src/sbt-test/tests/task/build.sbt create mode 100644 sbt/src/sbt-test/tests/task/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec.scala create mode 100644 sbt/src/sbt-test/tests/task/src/test/scala/com/test/TestSpec2.scala create mode 100644 sbt/src/sbt-test/tests/task/test create mode 100644 testing/agent/src/main/java/sbt/FrameworkWrapper.java diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index 3be3ad60d..c18b76283 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -4,7 +4,7 @@ package sbt import scala.collection.mutable -import org.scalatools.testing._ +import testing._ import java.net.ServerSocket import java.io._ import Tests.{Output => TestOutput, _} diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index c6549e4e2..3d1c6bcdc 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -11,7 +11,7 @@ package sbt import xsbti.api.Definition import ConcurrentRestrictions.Tag - import org.scalatools.testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint} + import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint} import java.io.File @@ -145,12 +145,12 @@ object Tests 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) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 257016892..990adf54e 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, AnnotatedFingerprint, SubclassFingerprint} import sys.error import scala.xml.NodeSeq 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/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/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/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 11a03927f..a04365e61 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; @@ -15,15 +15,18 @@ import java.util.ArrayList; import java.util.List; 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 +62,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 +146,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; @@ -141,44 +192,43 @@ public class ForkMain { final Framework framework; try { - framework = (Framework) Class.forName(implClassName).newInstance(); + Object rawFramework = Class.forName(implClassName).newInstance(); + if (rawFramework instanceof Framework) + framework = (Framework) rawFramework; + else + framework = new FrameworkWrapper((org.scalatools.testing.Framework) rawFramework); } catch (ClassNotFoundException e) { logError(os, "Framework implementation '" + implClassName + "' not present."); 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, new String[0], getClass().getClassLoader()); for (ForkTestDefinition test : filteredTests) - runTestSafe(test, runner, framework, frameworkArgs, os); + runTestSafe(test, runner, loggers, os); } write(os, ForkTags.Done); is.readObject(); } - void runTestSafe(ForkTestDefinition test, org.scalatools.testing.Runner runner, Framework framework, String[] frameworkArgs, ObjectOutputStream os) { + void runTestSafe(ForkTestDefinition test, Runner runner, Logger[] loggers, ObjectOutputStream os) { ForkEvent[] events; try { - events = runTest(test, runner, framework, frameworkArgs, os); + events = runTest(test, runner, loggers, os); } catch (Throwable t) { 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) { + ForkEvent[] runTest(ForkTestDefinition test, Runner runner, Logger[] loggers, 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 + "'")); - } + // TODO: To pass in correct explicitlySpecified and selectors + runner.task(test.name, test.fingerprint, false, new Selector[] { new SuiteSelector() }).execute(handler, loggers); return events.toArray(new ForkEvent[events.size()]); } void run(ObjectInputStream is, ObjectOutputStream os) throws Exception { @@ -198,22 +248,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, _} + import org.scalatools.testing.{Framework => OldFramework} import classpath.{ClasspathUtilities, DualLoader, FilteredLoader} object TestResult extends Enumeration @@ -17,7 +17,7 @@ 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") val Specs = new TestFramework("org.specs.runner.SpecsFramework") val Specs2 = new TestFramework("org.specs2.runner.SpecsFramework") val JUnit = new TestFramework("com.novocode.junit.JUnitFramework") @@ -27,8 +27,19 @@ case class TestFramework(val implClassName: 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 } + try + { + Some( + Class.forName(implClassName, true, loader).newInstance match { + case newFramework: Framework => newFramework + case oldFramework: OldFramework => new FrameworkWrapper(oldFramework) + } + ) + } + catch + { + case e: ClassNotFoundException => log.debug("Framework implementation '" + implClassName + "' not present."); None + } } } final class TestDefinition(val name: String, val fingerprint: Fingerprint) @@ -43,33 +54,24 @@ 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(framework: Framework, loader: ClassLoader, args: Array[String], listeners: Seq[TestReportListener], log: Logger) { + val delegate = framework.runner(args, Array.empty, loader) - final def run(testDefinition: TestDefinition, args: Seq[String]): TestResult.Value = + final def run(testDefinition: TestDefinition): TestResult.Value = { - 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)) + try { + // TODO: To pass in correct explicitlySpecified and selectors + delegate.task(testDefinition.name, testDefinition.fingerprint, false, Array(new SuiteSelector)).execute(handler, loggers.map(_.log).toArray) + } + finally loggers.foreach( _.flush() ) val event = TestEvent(results) safeListenersCall(_.testEvent( event )) event.result @@ -95,13 +97,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,21 +114,21 @@ 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 } @@ -160,7 +161,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 +172,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 = @@ -191,10 +192,10 @@ object TestFramework val testTasks = tests flatMap { case (framework, (testDefinitions, testArgs)) => - val runner = new TestRunner(framework, loader, listeners, log) + val runner = new TestRunner(framework, loader, testArgs.toArray, listeners, log) for(testDefinition <- testDefinitions) yield { - val runTest = () => withContextLoader(loader) { runner.run(testDefinition, testArgs) } + val runTest = () => withContextLoader(loader) { runner.run(testDefinition) } (testDefinition.name, runTest) } } @@ -210,8 +211,8 @@ 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) diff --git a/testing/src/main/scala/sbt/TestReportListener.scala b/testing/src/main/scala/sbt/TestReportListener.scala index d20dbbb76..c1de312fd 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 { @@ -38,9 +38,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 { @@ -91,11 +91,11 @@ class TestLogger(val logging: TestLogging) extends TestsListener 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 + val count = event.status match { + case TStatus.Error => errorsCount + case TStatus.Success => passedCount + case TStatus.Failure => failuresCount + case TStatus.Skipped => skippedCount } count.incrementAndGet() } From b83d378cd0f3c178c8e54477dfdd65344d25e673 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Fri, 5 Apr 2013 15:05:28 +0800 Subject: [PATCH 03/16] Changed ForkTests to call acceptorThread.join(), to make sure that acceptorThread is finished before Acceptor.result is called. --- main/actions/src/main/scala/sbt/ForkTests.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index c18b76283..17bf0a32e 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -72,7 +72,8 @@ 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) @@ -80,8 +81,11 @@ private[sbt] object ForkTests { val result = if (ec != 0) (TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> TestResult.Error)) - else + else { + // Need to wait acceptor thread to finish its business + acceptorThread.join() Acceptor.result + } testListeners.foreach(_.doComplete(result._1)) result } finally { From b2b9fb132ad7077fa8253c84acc6e3c1d7d47a0d Mon Sep 17 00:00:00 2001 From: cheeseng Date: Fri, 5 Apr 2013 15:55:17 +0800 Subject: [PATCH 04/16] Made to ForkMain's write method synchronized, to ensure the output stream won't corrupt when test framework implementation uses multi-threads to write event back to sbt. --- testing/agent/src/main/java/sbt/ForkMain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index a04365e61..6a9b1fe45 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -157,7 +157,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(); From d2e40c1c56ef94927bf1b11a6d81afc3f111049b Mon Sep 17 00:00:00 2001 From: cheeseng Date: Fri, 5 Apr 2013 15:20:04 +0800 Subject: [PATCH 05/16] Changed createTeskTasks in TestFramework.scala to reuse TestRunner instance when running different TestDefinition. --- .../sbt-test/tests/single-runner/build.sbt | 5 ++++ .../main/scala/custom/CustomReporter.scala | 28 +++++++++++++++++++ .../src/test/scala/com/test/TestSpec.scala | 12 ++++++++ .../src/test/scala/com/test/TestSpec2.scala | 12 ++++++++ sbt/src/sbt-test/tests/single-runner/test | 13 +++++++++ .../src/main/scala/sbt/TestFramework.scala | 19 ++++++++----- 6 files changed, 82 insertions(+), 7 deletions(-) create mode 100644 sbt/src/sbt-test/tests/single-runner/build.sbt create mode 100644 sbt/src/sbt-test/tests/single-runner/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec.scala create mode 100644 sbt/src/sbt-test/tests/single-runner/src/test/scala/com/test/TestSpec2.scala create mode 100644 sbt/src/sbt-test/tests/single-runner/test 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..f9552f01e --- /dev/null +++ b/sbt/src/sbt-test/tests/single-runner/src/main/scala/custom/CustomReporter.scala @@ -0,0 +1,28 @@ +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 runCompleted: RunCompleted => writeFile("target/RunCompleted", "RunCompleted") + 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/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index 2c28ddab6..311ba4044 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -184,6 +184,12 @@ object TestFramework private def createTestTasks(loader: ClassLoader, tests: Map[Framework, (Set[TestDefinition], Seq[String])], ordered: Seq[TestDefinition], log: Logger, listeners: Seq[TestReportListener]) = { val testsListeners = listeners collect { case tl: TestsListener => tl } + + val runnerMap = + tests map { case (framework, (testDefinitions, testArgs)) => + (framework, new TestRunner(framework, loader, testArgs.toArray, listeners, log)) + } + def foreachListenerSafe(f: TestsListener => Unit): () => Unit = () => safeForeach(testsListeners, log)(f) import TestResult.{Error,Passed,Failed} @@ -191,13 +197,12 @@ object TestFramework val startTask = foreachListenerSafe(_.doInit) val testTasks = tests flatMap { case (framework, (testDefinitions, testArgs)) => - - val runner = new TestRunner(framework, loader, testArgs.toArray, listeners, log) - for(testDefinition <- testDefinitions) yield - { - val runTest = () => withContextLoader(loader) { runner.run(testDefinition) } - (testDefinition.name, runTest) - } + val runner = runnerMap(framework) + for(testDefinition <- testDefinitions) yield + { + val runTest = () => withContextLoader(loader) { runner.run(testDefinition) } + (testDefinition.name, runTest) + } } val endTask = (result: TestResult.Value) => foreachListenerSafe(_.doComplete(result)) From 244e65cd79fe212eeed79c8c5146febe6354a7bc Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 3 Apr 2013 20:41:53 +0800 Subject: [PATCH 06/16] -Changed behavior so that only a single Runner instance is used to run tests in multiple test groups. -Added code to support remoteArgs in test-interface 1.0. --- .../src/main/scala/sbt/ForkTests.scala | 19 ++++++-------- main/actions/src/main/scala/sbt/Tests.scala | 10 ++++---- main/src/main/scala/sbt/Defaults.scala | 18 +++++++++++-- .../main/scala/custom/CustomReporter.scala | 1 - testing/agent/src/main/java/sbt/ForkMain.java | 3 ++- .../src/main/scala/sbt/TestFramework.scala | 25 ++++++++----------- 6 files changed, 41 insertions(+), 35 deletions(-) diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index 17bf0a32e..dcab26ade 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -11,7 +11,7 @@ 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,12 +25,6 @@ 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) { @@ -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.implClassName) + os.writeObject(mainRunner.args) + os.writeObject(remoteArgs) } os.flush() @@ -86,6 +82,7 @@ private[sbt] object ForkTests { acceptorThread.join() Acceptor.result } + testListeners.foreach(_.doComplete(result._1)) result } finally { diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 3d1c6bcdc..23c0c7d63 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -11,7 +11,7 @@ package sbt import xsbti.api.Definition import ConcurrentRestrictions.Tag - import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint} + import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner} import java.io.File @@ -43,7 +43,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] @@ -94,10 +94,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,7 +105,7 @@ 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 = diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 990adf54e..1dcbe9b3b 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -454,14 +454,28 @@ object Defaults extends BuildCommon } } + 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) 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 index f9552f01e..39c7085ac 100644 --- 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 @@ -21,7 +21,6 @@ class CustomReporter extends Reporter { def apply(event: Event) { event match { case runStarting: RunStarting => writeFile("target/RunStarting", "RunStarting") - case runCompleted: RunCompleted => writeFile("target/RunCompleted", "RunCompleted") case _ => } } diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 6a9b1fe45..4e3931ffc 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -189,6 +189,7 @@ public class ForkMain { for (int i = 0; i < nFrameworks; i++) { final String implClassName = (String) is.readObject(); final String[] frameworkArgs = (String[]) is.readObject(); + final String[] remoteFrameworkArgs = (String[]) is.readObject(); final Framework framework; try { @@ -208,7 +209,7 @@ public class ForkMain { if (matches(testFingerprint, test.fingerprint)) filteredTests.add(test); } } - final Runner runner = framework.runner(frameworkArgs, new String[0], getClass().getClassLoader()); + final Runner runner = framework.runner(frameworkArgs, remoteFrameworkArgs, getClass().getClassLoader()); for (ForkTestDefinition test : filteredTests) runTestSafe(test, runner, loggers, os); } diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index 311ba4044..bd85f5741 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -54,8 +54,7 @@ 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, args: Array[String], listeners: Seq[TestReportListener], log: Logger) { - val delegate = framework.runner(args, Array.empty, loader) +final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log: Logger) { final def run(testDefinition: TestDefinition): TestResult.Value = { @@ -133,7 +132,8 @@ object TestFramework 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, @@ -142,11 +142,11 @@ object TestFramework (() => Unit, Seq[(String, () => TestResult.Value)], 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)] = @@ -180,24 +180,19 @@ 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 } - val runnerMap = - tests map { case (framework, (testDefinitions, testArgs)) => - (framework, new TestRunner(framework, loader, testArgs.toArray, listeners, log)) - } - 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 = runnerMap(framework) + val runner = runners(framework) for(testDefinition <- testDefinitions) yield { val runTest = () => withContextLoader(loader) { runner.run(testDefinition) } From f38a244d0f6d0b63a6d1e85d060c1fa380c650af Mon Sep 17 00:00:00 2001 From: cheeseng Date: Thu, 4 Apr 2013 21:43:02 +0800 Subject: [PATCH 07/16] Added code to call Runner's done() method in both InProcess and SubProcess cases. --- main/src/main/scala/sbt/Defaults.scala | 8 +++- sbt/src/sbt-test/tests/done/build.sbt | 5 +++ .../main/scala/custom/CustomReporter.scala | 37 +++++++++++++++++++ .../src/test/scala/com/test/TestSpec.scala | 12 ++++++ .../src/test/scala/com/test/TestSpec2.scala | 12 ++++++ sbt/src/sbt-test/tests/done/test | 19 ++++++++++ testing/agent/src/main/java/sbt/ForkMain.java | 1 + 7 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 sbt/src/sbt-test/tests/done/build.sbt create mode 100644 sbt/src/sbt-test/tests/done/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec.scala create mode 100644 sbt/src/sbt-test/tests/done/src/test/scala/com/test/TestSpec2.scala create mode 100644 sbt/src/sbt-test/tests/done/test diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 1dcbe9b3b..14f3cb5cf 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 testing.{Framework, AnnotatedFingerprint, SubclassFingerprint} + import testing.{Framework, Runner, AnnotatedFingerprint, SubclassFingerprint} import sys.error import scala.xml.NodeSeq @@ -478,7 +478,11 @@ object Defaults extends BuildCommon Tests(frameworks, loader, runners, tests, config, s.log) } } - Tests.foldTasks(groupTasks, config.parallel) + val output = Tests.foldTasks(groupTasks, config.parallel) + runners foreach { case (tf, r) => + r.done() + } + output } def selectedFilter(args: Seq[String]): Seq[String => Boolean] = 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/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 4e3931ffc..9cdf1e744 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -212,6 +212,7 @@ public class ForkMain { final Runner runner = framework.runner(frameworkArgs, remoteFrameworkArgs, getClass().getClassLoader()); for (ForkTestDefinition test : filteredTests) runTestSafe(test, runner, loggers, os); + runner.done(); } write(os, ForkTags.Done); is.readObject(); From a76523a5f6566741eb2e708eb9285b5e92aca3eb Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 3 Apr 2013 21:07:31 +0800 Subject: [PATCH 08/16] Added >1 framework class per framework support, this enables sbt to support both old and new framework API at the same time. --- .../src/main/scala/sbt/ForkTests.scala | 2 +- main/actions/src/main/scala/sbt/Tests.scala | 2 +- testing/agent/src/main/java/sbt/ForkMain.java | 30 +++++++++----- .../src/main/scala/sbt/TestFramework.scala | 41 +++++++++++-------- 4 files changed, 46 insertions(+), 29 deletions(-) diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index dcab26ade..0748797dc 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -53,7 +53,7 @@ private[sbt] object ForkTests { os.writeInt(runners.size) for ((testFramework, mainRunner) <- runners) { val remoteArgs = mainRunner.remoteArgs() - os.writeObject(testFramework.implClassName) + os.writeObject(testFramework.implClassNames.toArray) os.writeObject(mainRunner.args) os.writeObject(remoteArgs) } diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 23c0c7d63..a368876c6 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -58,7 +58,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) diff --git a/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 9cdf1e744..43950dafd 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -168,6 +168,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}); } @@ -187,22 +190,27 @@ 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 { - Object rawFramework = Class.forName(implClassName).newInstance(); - if (rawFramework instanceof Framework) - framework = (Framework) rawFramework; - else - framework = new FrameworkWrapper((org.scalatools.testing.Framework) rawFramework); - } 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.fingerprints()) { for (ForkTestDefinition test : tests) { diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index bd85f5741..b59475983 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -8,6 +8,7 @@ package sbt import testing.{Logger=>TLogger, _} import org.scalatools.testing.{Framework => OldFramework} import classpath.{ClasspathUtilities, DualLoader, FilteredLoader} + import scala.annotation.tailrec object TestResult extends Enumeration { @@ -17,30 +18,38 @@ object TestResult extends Enumeration object TestFrameworks { val ScalaCheck = new TestFramework("org.scalacheck.ScalaCheckFramework") - val ScalaTest = new TestFramework("org.scalatest.tools.Framework") + 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 match { - case newFramework: Framework => newFramework - case oldFramework: OldFramework => new FrameworkWrapper(oldFramework) - } - ) - } - 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) { From 1ec2118219279344a135d64926091037d94b3872 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 10 Apr 2013 15:28:50 +0800 Subject: [PATCH 09/16] -Moved code that count test results from TestLogger to SuiteResult. -Print the results in Tests.showResult. --- .../src/main/scala/sbt/ForkTests.scala | 16 +++--- main/actions/src/main/scala/sbt/Tests.scala | 23 ++++++-- main/src/main/scala/sbt/Defaults.scala | 8 +-- .../tests/t543/project/Ticket543Test.scala | 6 +-- .../src/main/scala/sbt/TestFramework.scala | 16 +++--- .../main/scala/sbt/TestReportListener.scala | 53 ++++++++----------- 6 files changed, 65 insertions(+), 57 deletions(-) diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index 0748797dc..54e3a0258 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -30,8 +30,8 @@ private[sbt] object ForkTests { 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 = (overall(resultsAcc.values.map(_.result)), resultsAcc.toMap) def run: Unit = { val socket = try { @@ -76,7 +76,7 @@ private[sbt] object ForkTests { 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)) + (TestResult.Error, Map("Running java with options " + options.mkString(" ") + " failed with exit code " + ec -> SuiteResult.Error)) else { // Need to wait acceptor thread to finish its business acceptorThread.join() @@ -89,11 +89,11 @@ private[sbt] object ForkTests { server.close() } } else - (TestResult.Passed, Map.empty[String, TestResult.Value]) + (TestResult.Passed, Map.empty[String, SuiteResult]) } 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 { @@ -107,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 a368876c6..86801fdd5 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -19,7 +19,7 @@ sealed trait TestOption object Tests { // (overall result, individual results) - type Output = (TestResult.Value, Map[String,TestResult.Value]) + type Output = (TestResult.Value, Map[String,SuiteResult]) final case class Setup(setup: ClassLoader => Unit) extends TestOption def Setup(setup: () => Unit) = new Setup(_ => setup()) @@ -119,14 +119,14 @@ object Tests cleanupTasks map { _ => results } } } - type TestRunnable = (String, () => TestResult.Value) + type TestRunnable = (String, () => SuiteResult) 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) - def processResults(results: Iterable[(String, TestResult.Value)]): (TestResult.Value, Map[String, TestResult.Value]) = - (overall(results.map(_._2)), results.toMap) + def processResults(results: Iterable[(String, SuiteResult)]): (TestResult.Value, Map[String, SuiteResult]) = + (overall(results.map(_._2.result)), results.toMap) def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] = if (parallel) reduced(results.toIndexedSeq, { @@ -169,8 +169,21 @@ object Tests (tests, mains.toSet) } - def showResults(log: Logger, results: (TestResult.Value, Map[String, TestResult.Value]), noTestsMessage: =>String): Unit = + def showResults(log: Logger, results: (TestResult.Value, Map[String, SuiteResult]), noTestsMessage: =>String): Unit = { + val (skipped, errors, passed, failures) = + results._2.foldLeft((0, 0, 0, 0)) { case (acc, entry) => + val suiteResult = entry._2 + (acc._1 + suiteResult.skippedCount, acc._2 + suiteResult.errorCount, acc._3 + suiteResult.passedCount, acc._4 + suiteResult.failureCount) + } + val totalCount = failures + errors + skipped + passed + val postfix = "Total " + totalCount + ", Failed " + failures + ", Errors " + errors + ", Passed " + passed + ", Skipped " + skipped + results._1 match { + case TestResult.Error => log.error("Error: " + postfix) + case TestResult.Passed => log.info("Passed: " + postfix) + case TestResult.Failed => log.error("Failed: " + postfix) + } + if (results._2.isEmpty) log.info(noTestsMessage) else { diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 14f3cb5cf..e4d779518 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -479,10 +479,12 @@ object Defaults extends BuildCommon } } val output = Tests.foldTasks(groupTasks, config.parallel) - runners foreach { case (tf, r) => - r.done() + output map { out => + runners foreach { case (tf, r) => + r.done() + } + out } - output } def selectedFilter(args: Seq[String]): Seq[String => Boolean] = 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/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index b59475983..f70ab09c9 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -65,7 +65,7 @@ final class TestDefinition(val name: String, val fingerprint: Fingerprint) final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log: Logger) { - final def run(testDefinition: TestDefinition): TestResult.Value = + final def run(testDefinition: TestDefinition): SuiteResult = { log.debug("Running " + testDefinition) val name = testDefinition.name @@ -82,21 +82,21 @@ final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log finally loggers.foreach( _.flush() ) val event = TestEvent(results) safeListenersCall(_.testEvent( event )) - event.result + SuiteResult(results) } safeListenersCall(_.startGroup(name)) try { - val result = runTest().getOrElse(TestResult.Passed) - safeListenersCall(_.endGroup(name, result)) - result + val suiteResult = runTest() + safeListenersCall(_.endGroup(name, suiteResult.result)) + suiteResult } catch { case e: Throwable => safeListenersCall(_.endGroup(name, e)) - TestResult.Error + SuiteResult.Error } } @@ -148,7 +148,7 @@ object TestFramework log: Logger, listeners: Seq[TestReportListener], testArgsByFramework: Map[Framework, Seq[String]]): - (() => Unit, Seq[(String, () => TestResult.Value)], TestResult.Value => () => Unit) = + (() => Unit, Seq[(String, () => SuiteResult)], TestResult.Value => () => Unit) = { val arguments = testArgsByFramework withDefaultValue Nil val mappedTests = testMap(frameworks.values.toSeq, tests, arguments) @@ -158,7 +158,7 @@ object TestFramework 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, () => SuiteResult], inputs: Seq[TestDefinition]): Seq[(String, () => SuiteResult)] = 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]]): diff --git a/testing/src/main/scala/sbt/TestReportListener.scala b/testing/src/main/scala/sbt/TestReportListener.scala index c1de312fd..9cb4e66c5 100644 --- a/testing/src/main/scala/sbt/TestReportListener.scala +++ b/testing/src/main/scala/sbt/TestReportListener.scala @@ -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] @@ -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.status match { - case TStatus.Error => errorsCount - case TStatus.Success => passedCount - case TStatus.Failure => failuresCount - case TStatus.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)) } From f862e649114927af344dd55e9a4ec347da3c9d6e Mon Sep 17 00:00:00 2001 From: cheeseng Date: Thu, 4 Apr 2013 11:30:59 +0800 Subject: [PATCH 10/16] Added support of custom summary message returned from Runner.done method. --- .../src/main/scala/sbt/ForkTests.scala | 8 +- main/actions/src/main/scala/sbt/Tests.scala | 77 ++++++++++++------- main/src/main/scala/sbt/Defaults.scala | 13 ++-- 3 files changed, 60 insertions(+), 38 deletions(-) diff --git a/main/actions/src/main/scala/sbt/ForkTests.scala b/main/actions/src/main/scala/sbt/ForkTests.scala index 54e3a0258..d4421e713 100755 --- a/main/actions/src/main/scala/sbt/ForkTests.scala +++ b/main/actions/src/main/scala/sbt/ForkTests.scala @@ -31,7 +31,7 @@ private[sbt] object ForkTests { val server = new ServerSocket(0) object Acceptor extends Runnable { val resultsAcc = mutable.Map.empty[String, SuiteResult] - lazy val result = (overall(resultsAcc.values.map(_.result)), resultsAcc.toMap) + lazy val result = TestOutput(overall(resultsAcc.values.map(_.result)), resultsAcc.toMap, Iterable.empty) def run: Unit = { val socket = try { @@ -76,20 +76,20 @@ private[sbt] object ForkTests { 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 -> SuiteResult.Error)) + 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, SuiteResult]) + TestOutput(TestResult.Passed, Map.empty[String, SuiteResult], Iterable.empty) } tagw (config.tags: _*) } } diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 86801fdd5..987e39126 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -19,7 +19,8 @@ sealed trait TestOption object Tests { // (overall result, individual results) - type Output = (TestResult.Value, Map[String,SuiteResult]) + 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()) @@ -115,7 +116,7 @@ object Tests makeSerial(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 } } } @@ -125,12 +126,12 @@ object Tests def makeSerial(runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) = task { runnables map { case (name, test) => (name, test()) } } dependsOn(setupTasks) - def processResults(results: Iterable[(String, SuiteResult)]): (TestResult.Value, Map[String, SuiteResult]) = - (overall(results.map(_._2.result)), results.toMap) - def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] = + 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,8 +139,8 @@ 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 = @@ -169,27 +170,45 @@ object Tests (tests, mains.toSet) } - def showResults(log: Logger, results: (TestResult.Value, Map[String, SuiteResult]), noTestsMessage: =>String): Unit = + def showResults(log: Logger, results: Output, noTestsMessage: =>String): Unit = { - val (skipped, errors, passed, failures) = - results._2.foldLeft((0, 0, 0, 0)) { case (acc, entry) => - val suiteResult = entry._2 - (acc._1 + suiteResult.skippedCount, acc._2 + suiteResult.errorCount, acc._3 + suiteResult.passedCount, acc._4 + suiteResult.failureCount) - } - val totalCount = failures + errors + skipped + passed - val postfix = "Total " + totalCount + ", Failed " + failures + ", Errors " + errors + ", Passed " + passed + ", Skipped " + skipped - results._1 match { - case TestResult.Error => log.error("Error: " + postfix) - case TestResult.Passed => log.info("Passed: " + postfix) - case TestResult.Failed => log.error("Failed: " + postfix) + 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.") } - if (results._2.isEmpty) + 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) @@ -197,17 +216,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 e4d779518..2287f8a34 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -446,9 +446,9 @@ 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) } @@ -480,10 +480,11 @@ object Defaults extends BuildCommon } val output = Tests.foldTasks(groupTasks, config.parallel) output map { out => - runners foreach { case (tf, r) => - r.done() - } - out + val summaries = + runners map { case (tf, r) => + Tests.Summary(frameworks(tf).name, r.done()) + } + out.copy(summaries = summaries) } } From ad18b4f3aecce5dacc9a7c8a3b53300ee43cca09 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Thu, 4 Apr 2013 10:48:15 +0800 Subject: [PATCH 11/16] Added code to tag test task using Array[String] returned from Task.tags method. --- main/actions/src/main/scala/sbt/Tests.scala | 9 ++++--- .../src/main/scala/sbt/TestFramework.scala | 25 +++++++++++-------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 987e39126..6c8542e85 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -11,7 +11,7 @@ package sbt import xsbti.api.Definition import ConcurrentRestrictions.Tag - import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner} + import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner, Task => TestTask} import java.io.File @@ -120,11 +120,12 @@ object Tests cleanupTasks map { _ => results } } } - type TestRunnable = (String, () => SuiteResult) + type TestRunnable = (String, TestFunction) 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 } + runnables map { case (name, test) => task { (name, test.apply()) } tagw(tags : _*) tag(test.tags map (ConcurrentRestrictions.Tag(_)) : _*) 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) + task { runnables map { case (name, test) => (name, test.apply()) } } dependsOn(setupTasks) def processResults(results: Iterable[(String, SuiteResult)]): Output = Output(overall(results.map(_._2.result)), results.toMap, Iterable.empty) diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index f70ab09c9..59bec069a 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -5,7 +5,7 @@ package sbt import java.io.File import java.net.URLClassLoader - import testing.{Logger=>TLogger, _} + import testing.{Logger=>TLogger, Task => TestTask, _} import org.scalatools.testing.{Framework => OldFramework} import classpath.{ClasspathUtilities, DualLoader, FilteredLoader} import scala.annotation.tailrec @@ -65,7 +65,10 @@ final class TestDefinition(val name: String, val fingerprint: Fingerprint) final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log: Logger) { - final def run(testDefinition: TestDefinition): SuiteResult = + 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 = { log.debug("Running " + testDefinition) val name = testDefinition.name @@ -75,10 +78,7 @@ final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log val results = new scala.collection.mutable.ListBuffer[Event] val handler = new EventHandler { def handle(e:Event){ results += e } } val loggers = listeners.flatMap(_.contentLogger(testDefinition)) - try { - // TODO: To pass in correct explicitlySpecified and selectors - delegate.task(testDefinition.name, testDefinition.fingerprint, false, Array(new SuiteSelector)).execute(handler, loggers.map(_.log).toArray) - } + try testTask.execute(handler, loggers.map(_.log).toArray) finally loggers.foreach( _.flush() ) val event = TestEvent(results) safeListenersCall(_.testEvent( event )) @@ -148,7 +148,7 @@ object TestFramework log: Logger, listeners: Seq[TestReportListener], testArgsByFramework: Map[Framework, Seq[String]]): - (() => Unit, Seq[(String, () => SuiteResult)], TestResult.Value => () => Unit) = + (() => Unit, Seq[(String, TestFunction)], TestResult.Value => () => Unit) = { val arguments = testArgsByFramework withDefaultValue Nil val mappedTests = testMap(frameworks.values.toSeq, tests, arguments) @@ -158,7 +158,7 @@ object TestFramework 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, () => SuiteResult], inputs: Seq[TestDefinition]): Seq[(String, () => SuiteResult)] = + 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]]): @@ -204,8 +204,9 @@ object TestFramework val runner = runners(framework) for(testDefinition <- testDefinitions) yield { - val runTest = () => withContextLoader(loader) { runner.run(testDefinition) } - (testDefinition.name, runTest) + val task = withContextLoader(loader) { runner.task(testDefinition) } + val testFunction = new TestFunction(() => withContextLoader(loader) { runner.run(testDefinition, task) }) { def tags = task.tags } + (testDefinition.name, testFunction) } } @@ -228,3 +229,7 @@ object TestFramework ClasspathUtilities.filterByClasspath(interfaceJar +: classpath, main) } } + +abstract class TestFunction(val apply: () => SuiteResult) { + def tags: Array[String] +} From 5df0deacf0be5e34a20064766327691622d62d92 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 27 Mar 2013 14:05:57 +0800 Subject: [PATCH 12/16] Added .gitattributes file. --- .gitattributes | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .gitattributes 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 From df9a475158ff121fd15fa0d540f1d0e7bcb15d56 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Thu, 4 Apr 2013 10:39:29 +0800 Subject: [PATCH 13/16] Normalize line endings. --- sbt/src/sbt-test/actions/reload/test | 24 +++---- .../basic/src/main/scala/testCase/JFoo.java | 20 +++--- .../src/main/scala/testCase/annotations.scala | 14 ++-- .../force/project/TestProject.scala | 52 +++++++-------- .../sbt-test/dependency-management/force/test | 22 +++---- .../project/session-save/build.check.1 | 8 +-- .../project/session-save/build.check.2 | 8 +-- .../project/session-save/build.check.3 | 12 ++-- .../sbt-test/project/session-save/build.sbt | 12 ++-- .../project/session-save/project/Build.scala | 16 ++--- sbt/src/sbt-test/project/session-save/test | 48 +++++++------- .../sbt-test/tests/test-quick/changed/A.scala | 6 +- .../sbt-test/tests/test-quick/changed/B.scala | 10 +-- .../tests/test-quick/changed/Base.scala | 14 ++-- .../tests/test-quick/src/main/scala/A.scala | 6 +- .../tests/test-quick/src/main/scala/B.scala | 8 +-- .../test-quick/src/test/scala/Base.scala | 10 +-- .../test-quick/src/test/scala/Create.scala | 22 +++---- .../test-quick/src/test/scala/Delete.scala | 20 +++--- sbt/src/sbt-test/tests/test-quick/test | 66 +++++++++---------- .../src/main/scala/sbt/Positions.scala | 40 +++++------ 21 files changed, 219 insertions(+), 219 deletions(-) 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/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/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 +} From 3109912d00807151aa536fc0e17976e77efc3d74 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 3 Apr 2013 21:00:59 +0800 Subject: [PATCH 14/16] Added support of nested test tasks when tests are executed InProcess and sequentially. --- main/actions/src/main/scala/sbt/Tests.scala | 25 ++++++++-- .../tests/nested-inproc-seq/build.sbt | 7 +++ .../main/scala/custom/CustomReporter.scala | 30 ++++++++++++ .../src/test/scala/com/test/NestedSpecs.scala | 17 +++++++ sbt/src/sbt-test/tests/nested-inproc-seq/test | 48 +++++++++++++++++++ .../src/main/scala/sbt/TestFramework.scala | 28 ++++++----- 6 files changed, 140 insertions(+), 15 deletions(-) create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/build.sbt create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/src/test/scala/com/test/NestedSpecs.scala create mode 100644 sbt/src/sbt-test/tests/nested-inproc-seq/test diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 6c8542e85..9da8786dc 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -12,6 +12,7 @@ package sbt import ConcurrentRestrictions.Tag import testing.{AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner, Task => TestTask} + import scala.annotation.tailrec import java.io.File @@ -113,7 +114,7 @@ object Tests if(config.parallel) makeParallel(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.overall)) @@ -122,10 +123,26 @@ object Tests } type TestRunnable = (String, TestFunction) def makeParallel(runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) = - runnables map { case (name, test) => task { (name, test.apply()) } tagw(tags : _*) tag(test.tags map (ConcurrentRestrictions.Tag(_)) : _*) dependsOn setupTasks named name } + runnables map { case (name, test) => task { (name, test.apply()._1) } tagw(tags : _*) tag(test.tags map (ConcurrentRestrictions.Tag(_)) : _*) dependsOn setupTasks named name } - def makeSerial(runnables: Seq[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) = - task { runnables map { case (name, test) => (name, test.apply()) } } dependsOn(setupTasks) + 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 = + nestedTasks.view.zipWithIndex map { case (nt, idx) => + (hd._1, TestFramework.createTestFunction(loader, new TestDefinition(testFun.testDefinition.name + "-" + idx, testFun.testDefinition.fingerprint), testFun.runner, nt)) + } + 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) 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/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index 59bec069a..ec3c4ef6a 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -68,7 +68,7 @@ final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log 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 = + final def run(testDefinition: TestDefinition, testTask: TestTask): (SuiteResult, Seq[TestTask]) = { log.debug("Running " + testDefinition) val name = testDefinition.name @@ -78,25 +78,26 @@ final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log val results = new scala.collection.mutable.ListBuffer[Event] val handler = new EventHandler { def handle(e:Event){ results += e } } val loggers = listeners.flatMap(_.contentLogger(testDefinition)) - try testTask.execute(handler, loggers.map(_.log).toArray) - finally loggers.foreach( _.flush() ) + val nestedTasks = + try testTask.execute(handler, loggers.map(_.log).toArray) + finally loggers.foreach( _.flush() ) val event = TestEvent(results) safeListenersCall(_.testEvent( event )) - SuiteResult(results) + (SuiteResult(results), nestedTasks.toSeq) } safeListenersCall(_.startGroup(name)) try { - val suiteResult = runTest() + val (suiteResult, nestedTasks) = runTest() safeListenersCall(_.endGroup(name, suiteResult.result)) - suiteResult + (suiteResult, nestedTasks) } catch { case e: Throwable => safeListenersCall(_.endGroup(name, e)) - SuiteResult.Error + (SuiteResult.Error, Seq.empty[TestTask]) } } @@ -204,8 +205,8 @@ object TestFramework val runner = runners(framework) for(testDefinition <- testDefinitions) yield { - val task = withContextLoader(loader) { runner.task(testDefinition) } - val testFunction = new TestFunction(() => withContextLoader(loader) { runner.run(testDefinition, task) }) { def tags = task.tags } + val testTask = withContextLoader(loader) { runner.task(testDefinition) } + val testFunction = createTestFunction(loader, testDefinition, runner, testTask) (testDefinition.name, testFunction) } } @@ -228,8 +229,13 @@ object TestFramework 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 apply: () => SuiteResult) { - def tags: Array[String] +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] } From a920c739d6105cda3e4eaf5738f345a4992e8c24 Mon Sep 17 00:00:00 2001 From: cheeseng Date: Fri, 29 Mar 2013 21:02:43 +0800 Subject: [PATCH 15/16] Added support of nested test tasks when tests are executed in SubProcess. --- .../sbt-test/tests/nested-subproc/build.sbt | 7 +++ .../main/scala/custom/CustomReporter.scala | 30 +++++++++ .../src/test/scala/com/test/NestedSpecs.scala | 17 ++++++ sbt/src/sbt-test/tests/nested-subproc/test | 48 +++++++++++++++ testing/agent/src/main/java/sbt/ForkMain.java | 61 ++++++++++++++++--- 5 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 sbt/src/sbt-test/tests/nested-subproc/build.sbt create mode 100644 sbt/src/sbt-test/tests/nested-subproc/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/nested-subproc/src/test/scala/com/test/NestedSpecs.scala create mode 100644 sbt/src/sbt-test/tests/nested-subproc/test 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/testing/agent/src/main/java/sbt/ForkMain.java b/testing/agent/src/main/java/sbt/ForkMain.java index 43950dafd..8ec30b795 100755 --- a/testing/agent/src/main/java/sbt/ForkMain.java +++ b/testing/agent/src/main/java/sbt/ForkMain.java @@ -13,6 +13,7 @@ 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 SubclassFingerprint, Serializable { @@ -225,21 +226,63 @@ public class ForkMain { write(os, ForkTags.Done); is.readObject(); } + 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) { - ForkEvent[] events; try { - events = runTest(test, runner, loggers, 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, Runner runner, Logger[] loggers, ObjectOutputStream os) { - final List events = new ArrayList(); - EventHandler handler = new EventHandler() { public void handle(Event e){ events.add(new ForkEvent(e)); } }; - // TODO: To pass in correct explicitlySpecified and selectors - runner.task(test.name, test.fingerprint, false, new Selector[] { new SuiteSelector() }).execute(handler, loggers); - return events.toArray(new ForkEvent[events.size()]); + return nestedTasks; } void run(ObjectInputStream is, ObjectOutputStream os) throws Exception { try { From 48fb0c4ed6ed38283506a2960c02508603885b7d Mon Sep 17 00:00:00 2001 From: cheeseng Date: Wed, 3 Apr 2013 17:04:40 +0800 Subject: [PATCH 16/16] Initial working version of makeParallel that support nested tasks. --- main/actions/src/main/scala/sbt/Tests.scala | 33 ++++++++++--- .../tests/nested-inproc-par/build.sbt | 7 +++ .../main/scala/custom/CustomReporter.scala | 30 ++++++++++++ .../src/test/scala/com/test/NestedSpecs.scala | 17 +++++++ sbt/src/sbt-test/tests/nested-inproc-par/test | 48 +++++++++++++++++++ 5 files changed, 128 insertions(+), 7 deletions(-) create mode 100644 sbt/src/sbt-test/tests/nested-inproc-par/build.sbt create mode 100644 sbt/src/sbt-test/tests/nested-inproc-par/src/main/scala/custom/CustomReporter.scala create mode 100644 sbt/src/sbt-test/tests/nested-inproc-par/src/test/scala/com/test/NestedSpecs.scala create mode 100644 sbt/src/sbt-test/tests/nested-inproc-par/test diff --git a/main/actions/src/main/scala/sbt/Tests.scala b/main/actions/src/main/scala/sbt/Tests.scala index 9da8786dc..4a7eda96a 100644 --- a/main/actions/src/main/scala/sbt/Tests.scala +++ b/main/actions/src/main/scala/sbt/Tests.scala @@ -112,7 +112,7 @@ object Tests 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(loader, runnables, setupTasks, config.tags) val taggedMainTasks = mainTasks.tagw(config.tags : _*) @@ -122,8 +122,30 @@ object Tests } } type TestRunnable = (String, TestFunction) - def makeParallel(runnables: Iterable[TestRunnable], setupTasks: Task[Unit], tags: Seq[(Tag,Int)]) = - runnables map { case (name, test) => task { (name, test.apply()._1) } tagw(tags : _*) tag(test.tags map (ConcurrentRestrictions.Tag(_)) : _*) dependsOn setupTasks named name } + + 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 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)]] = { @@ -133,10 +155,7 @@ object Tests case hd :: rst => val testFun = hd._2 val (result, nestedTasks) = testFun.apply() - val nestedRunnables = - nestedTasks.view.zipWithIndex map { case (nt, idx) => - (hd._1, TestFramework.createTestFunction(loader, new TestDefinition(testFun.testDefinition.name + "-" + idx, testFun.testDefinition.fingerprint), testFun.runner, nt)) - } + val nestedRunnables = createNestedRunnables(testFun.testDefinition.name, loader, testFun, nestedTasks) processRunnable(nestedRunnables.toList ::: rst, (hd._1, result) :: acc) case Nil => acc } 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