From 093fc061ccf72ea44d288df13515b79112fde6f1 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 1 Apr 2017 17:19:45 -0400 Subject: [PATCH] Send testing events to logger --- build.sbt | 5 +- .../src/main/scala/sbt/ForkTests.scala | 1 + .../src/main/scala/sbt/TestResultLogger.scala | 3 +- main-actions/src/main/scala/sbt/Tests.scala | 22 ++-- main/src/main/scala/sbt/Defaults.scala | 1 + project/ContrabandConfig.scala | 1 + .../testing/EndTestGroupErrorEvent.scala | 37 +++++++ .../protocol/testing/EndTestGroupEvent.scala | 37 +++++++ .../testing/StartTestGroupEvent.scala | 33 ++++++ .../protocol/testing/TestCompleteEvent.scala | 33 ++++++ .../sbt/protocol/testing/TestInitEvent.scala | 30 ++++++ .../sbt/protocol/testing/TestItemDetail.scala | 54 ++++++++++ .../sbt/protocol/testing/TestItemEvent.scala | 41 +++++++ .../sbt/protocol/testing/TestMessage.scala | 26 +++++ .../sbt/protocol/testing/TestResult.scala | 15 +++ .../protocol/testing/TestStringEvent.scala | 32 ++++++ .../codec/EndTestGroupErrorEventFormats.scala | 29 +++++ .../codec/EndTestGroupEventFormats.scala | 29 +++++ .../protocol/testing/codec/JsonProtocol.scala | 19 ++++ .../codec/StartTestGroupEventFormats.scala | 27 +++++ .../codec/TestCompleteEventFormats.scala | 27 +++++ .../testing/codec/TestInitEventFormats.scala | 27 +++++ .../testing/codec/TestItemDetailFormats.scala | 31 ++++++ .../testing/codec/TestItemEventFormats.scala | 29 +++++ .../testing/codec/TestMessageFormats.scala | 10 ++ .../testing/codec/TestResultFormats.scala | 31 ++++++ .../codec/TestStringEventFormats.scala | 27 +++++ testing/src/main/contraband/testing.contra | 65 +++++++++++ .../scala/sbt/JUnitXmlTestsListener.scala | 5 +- .../src/main/scala/sbt/TestFramework.scala | 23 ++-- .../main/scala/sbt/TestReportListener.scala | 74 ++----------- .../main/scala/sbt/TestStatusReporter.scala | 5 +- .../sbt/internal/testing/StatusFormats.scala | 38 +++++++ .../sbt/internal/testing/TestLogger.scala | 101 ++++++++++++++++++ 34 files changed, 878 insertions(+), 90 deletions(-) create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/TestResult.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupErrorEventFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupEventFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/JsonProtocol.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/StartTestGroupEventFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestCompleteEventFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestInitEventFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemDetailFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemEventFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestMessageFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestResultFormats.scala create mode 100644 testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestStringEventFormats.scala create mode 100644 testing/src/main/contraband/testing.contra create mode 100644 testing/src/main/scala/sbt/internal/testing/StatusFormats.scala create mode 100644 testing/src/main/scala/sbt/internal/testing/TestLogger.scala diff --git a/build.sbt b/build.sbt index d4fc0cbab..0ff74cdd6 100644 --- a/build.sbt +++ b/build.sbt @@ -101,11 +101,14 @@ lazy val bundledLauncherProj = // Runner for uniform test interface lazy val testingProj = (project in file("testing")). + enablePlugins(ContrabandPlugin, JsonCodecPlugin). dependsOn(testAgentProj). settings( baseSettings, name := "Testing", - libraryDependencies ++= Seq(testInterface,launcherInterface) + libraryDependencies ++= Seq(testInterface,launcherInterface, sjsonNewScalaJson), + sourceManaged in (Compile, generateContrabands) := baseDirectory.value / "src" / "main" / "contraband-scala", + contrabandFormatsForType in generateContrabands in Compile := ContrabandConfig.getFormats ). configure(addSbtIO, addSbtCompilerClasspath, addSbtUtilLogging) diff --git a/main-actions/src/main/scala/sbt/ForkTests.scala b/main-actions/src/main/scala/sbt/ForkTests.scala index fce5e445f..bcf395193 100755 --- a/main-actions/src/main/scala/sbt/ForkTests.scala +++ b/main-actions/src/main/scala/sbt/ForkTests.scala @@ -12,6 +12,7 @@ import Tests.{ Output => TestOutput, _ } import sbt.io.IO import sbt.util.Logger import sbt.ConcurrentRestrictions.Tag +import sbt.protocol.testing._ private[sbt] object ForkTests { def apply(runners: Map[TestFramework, Runner], tests: List[TestDefinition], config: Execution, classpath: Seq[File], fork: ForkOptions, log: Logger, tag: Tag): Task[TestOutput] = { diff --git a/main-actions/src/main/scala/sbt/TestResultLogger.scala b/main-actions/src/main/scala/sbt/TestResultLogger.scala index c0a9cc4c9..9715c42c5 100644 --- a/main-actions/src/main/scala/sbt/TestResultLogger.scala +++ b/main-actions/src/main/scala/sbt/TestResultLogger.scala @@ -2,6 +2,7 @@ package sbt import sbt.Tests.{ Output, Summary } import sbt.util.{ Level, Logger } +import sbt.protocol.testing.TestResult /** * Logs information about tests after they finish. @@ -138,7 +139,7 @@ object TestResultLogger { }) val printFailures = TestResultLogger((log, results, _) => { - def select(resultTpe: TestResult.Value) = results.events collect { + def select(resultTpe: TestResult) = results.events collect { case (name, tpe) if tpe.result == resultTpe => scala.reflect.NameTransformer.decode(name) } diff --git a/main-actions/src/main/scala/sbt/Tests.scala b/main-actions/src/main/scala/sbt/Tests.scala index bdcd79308..5911726f0 100644 --- a/main-actions/src/main/scala/sbt/Tests.scala +++ b/main-actions/src/main/scala/sbt/Tests.scala @@ -15,7 +15,9 @@ import ConcurrentRestrictions.Tag import testing.{ AnnotatedFingerprint, Fingerprint, Framework, SubclassFingerprint, Runner, TaskDef, SuiteSelector, Task => TestTask } import scala.annotation.tailrec +import sbt.internal.util.ManagedLogger import sbt.util.Logger +import sbt.protocol.testing.TestResult sealed trait TestOption object Tests { @@ -26,7 +28,7 @@ object Tests { * @param events The result of each test group (suite) executed during this test run. * @param summaries Explicit summaries directly provided by test frameworks. This may be empty, in which case a default summary will be generated. */ - final case class Output(overall: TestResult.Value, events: Map[String, SuiteResult], summaries: Iterable[Summary]) + final case class Output(overall: TestResult, events: Map[String, SuiteResult], summaries: Iterable[Summary]) /** * Summarizes a test run. @@ -162,7 +164,7 @@ object Tests { in.filter(t => seen.add(f(t))) } - def apply(frameworks: Map[TestFramework, Framework], testLoader: ClassLoader, runners: Map[TestFramework, Runner], 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: ManagedLogger): Task[Output] = { val o = processOptions(config, discovered, log) testTask(testLoader, frameworks, runners, o.tests, o.setup, o.cleanup, log, o.testListeners, config) @@ -170,7 +172,7 @@ object Tests { 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], config: Execution): Task[Output] = + log: ManagedLogger, testListeners: Seq[TestReportListener], config: Execution): Task[Output] = { def fj(actions: Iterable[() => Unit]): Task[Unit] = nop.dependsOn(actions.toSeq.fork(_()): _*) def partApp(actions: Iterable[ClassLoader => Unit]) = actions.toSeq map { a => () => a(loader) } @@ -249,12 +251,20 @@ object Tests { def processResults(results: Iterable[(String, SuiteResult)]): Output = Output(overall(results.map(_._2.result)), results.toMap, Iterable.empty) + + private def severity(r: TestResult): Int = + r match { + case TestResult.Passed => 0 + case TestResult.Failed => 1 + case TestResult.Error => 2 + } + def foldTasks(results: Seq[Task[Output]], parallel: Boolean): Task[Output] = if (results.isEmpty) task { Output(TestResult.Passed, Map.empty, Nil) } else if (parallel) reduced(results.toIndexedSeq, { - case (Output(v1, m1, _), Output(v2, m2, _)) => Output(if (v1.id < v2.id) v2 else v1, m1 ++ m2, Iterable.empty) + case (Output(v1, m1, _), Output(v2, m2, _)) => Output(if (severity(v1) < severity(v2)) v2 else v1, m1 ++ m2, Iterable.empty) }) else { def sequence(tasks: List[Task[Output]], acc: List[Output]): Task[List[Output]] = tasks match { @@ -266,8 +276,8 @@ object Tests { Output(overall(rs), ms reduce (_ ++ _), Iterable.empty) } } - def overall(results: Iterable[TestResult.Value]): TestResult.Value = - (TestResult.Passed /: results) { (acc, result) => if (acc.id < result.id) result else acc } + def overall(results: Iterable[TestResult]): TestResult = + ((TestResult.Passed: TestResult) /: results) { (acc, result) => if (severity(acc) < severity(result)) result else acc } def discover(frameworks: Seq[Framework], analysis: CompileAnalysis, log: Logger): (Seq[TestDefinition], Set[String]) = discover(frameworks flatMap TestFramework.getFingerprints, allDefs(analysis), log) diff --git a/main/src/main/scala/sbt/Defaults.scala b/main/src/main/scala/sbt/Defaults.scala index 8d6ab9ef2..50beb5a50 100755 --- a/main/src/main/scala/sbt/Defaults.scala +++ b/main/src/main/scala/sbt/Defaults.scala @@ -17,6 +17,7 @@ import sbt.internal.librarymanagement._ import sbt.internal.librarymanagement.mavenint.{ PomExtraDependencyAttributes, SbtPomExtraProperties } import sbt.internal.librarymanagement.syntax._ import sbt.internal.librarymanagement.{ CustomPomParser, DependencyFilter } +import sbt.internal.testing.TestLogger import sbt.internal.util._ import sbt.internal.util.Attributed.data import sbt.internal.util.CacheImplicits._ diff --git a/project/ContrabandConfig.scala b/project/ContrabandConfig.scala index 7fb8a2b8e..f1bbd9d42 100644 --- a/project/ContrabandConfig.scala +++ b/project/ContrabandConfig.scala @@ -22,6 +22,7 @@ object ContrabandConfig { case "Option" | "Set" | "scala.Vector" => { tpe => getFormats(oneArg(tpe)) } case "Map" | "Tuple2" | "scala.Tuple2" => { tpe => twoArgs(tpe).flatMap(getFormats) } case "Int" | "Long" => { _ => Nil } + case "sbt.testing.Status" => { _ => "sbt.internal.testing.StatusFormats" :: Nil } case "scala.json.ast.unsafe.JValue" => { _ => "sbt.internal.JValueFormat" :: Nil } } diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala new file mode 100644 index 000000000..905567dfd --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupErrorEvent.scala @@ -0,0 +1,37 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Called if test completed with an error. */ +final class EndTestGroupErrorEvent private ( + val name: String, + val error: String) extends sbt.protocol.testing.TestMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: EndTestGroupErrorEvent => (this.name == x.name) && (this.error == x.error) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (17 + name.##) + error.##) + } + override def toString: String = { + "EndTestGroupErrorEvent(" + name + ", " + error + ")" + } + protected[this] def copy(name: String = name, error: String = error): EndTestGroupErrorEvent = { + new EndTestGroupErrorEvent(name, error) + } + def withName(name: String): EndTestGroupErrorEvent = { + copy(name = name) + } + def withError(error: String): EndTestGroupErrorEvent = { + copy(error = error) + } +} +object EndTestGroupErrorEvent { + + def apply(name: String, error: String): EndTestGroupErrorEvent = new EndTestGroupErrorEvent(name, error) +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala new file mode 100644 index 000000000..c42958747 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/EndTestGroupEvent.scala @@ -0,0 +1,37 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Called if test completed. */ +final class EndTestGroupEvent private ( + val name: String, + val result: sbt.protocol.testing.TestResult) extends sbt.protocol.testing.TestMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: EndTestGroupEvent => (this.name == x.name) && (this.result == x.result) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (17 + name.##) + result.##) + } + override def toString: String = { + "EndTestGroupEvent(" + name + ", " + result + ")" + } + protected[this] def copy(name: String = name, result: sbt.protocol.testing.TestResult = result): EndTestGroupEvent = { + new EndTestGroupEvent(name, result) + } + def withName(name: String): EndTestGroupEvent = { + copy(name = name) + } + def withResult(result: sbt.protocol.testing.TestResult): EndTestGroupEvent = { + copy(result = result) + } +} +object EndTestGroupEvent { + + def apply(name: String, result: sbt.protocol.testing.TestResult): EndTestGroupEvent = new EndTestGroupEvent(name, result) +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala new file mode 100644 index 000000000..c8326429d --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/StartTestGroupEvent.scala @@ -0,0 +1,33 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Called for each class or equivalent grouping. */ +final class StartTestGroupEvent private ( + val name: String) extends sbt.protocol.testing.TestMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: StartTestGroupEvent => (this.name == x.name) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + name.##) + } + override def toString: String = { + "StartTestGroupEvent(" + name + ")" + } + protected[this] def copy(name: String = name): StartTestGroupEvent = { + new StartTestGroupEvent(name) + } + def withName(name: String): StartTestGroupEvent = { + copy(name = name) + } +} +object StartTestGroupEvent { + + def apply(name: String): StartTestGroupEvent = new StartTestGroupEvent(name) +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala new file mode 100644 index 000000000..c2afaceac --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestCompleteEvent.scala @@ -0,0 +1,33 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Called once, at end of the testing. */ +final class TestCompleteEvent private ( + val result: sbt.protocol.testing.TestResult) extends sbt.protocol.testing.TestMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: TestCompleteEvent => (this.result == x.result) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + result.##) + } + override def toString: String = { + "TestCompleteEvent(" + result + ")" + } + protected[this] def copy(result: sbt.protocol.testing.TestResult = result): TestCompleteEvent = { + new TestCompleteEvent(result) + } + def withResult(result: sbt.protocol.testing.TestResult): TestCompleteEvent = { + copy(result = result) + } +} +object TestCompleteEvent { + + def apply(result: sbt.protocol.testing.TestResult): TestCompleteEvent = new TestCompleteEvent(result) +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala new file mode 100644 index 000000000..e1bbe4425 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestInitEvent.scala @@ -0,0 +1,30 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Called once, at beginning of the testing. */ +final class TestInitEvent private () extends sbt.protocol.testing.TestMessage() with Serializable { + + + +override def equals(o: Any): Boolean = o match { + case x: TestInitEvent => true + case _ => false +} +override def hashCode: Int = { + 17 +} +override def toString: String = { + "TestInitEvent()" +} +protected[this] def copy(): TestInitEvent = { + new TestInitEvent() +} + +} +object TestInitEvent { + + def apply(): TestInitEvent = new TestInitEvent() +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala new file mode 100644 index 000000000..105378904 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemDetail.scala @@ -0,0 +1,54 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Mini version of sbt.testing.Event */ +final class TestItemDetail private ( + /** + * The fully qualified name of a class that can rerun the suite or test + * about which an event was fired. + */ + val fullyQualifiedName: String, + /** Indicates whether the event represents a test success, failure, error, skipped, ignored, canceled, pending. */ + val status: sbt.testing.Status, + /** + * An amount of time, in milliseconds, that was required to complete the action reported by this event. + * None, if no duration was available. + */ + val duration: Option[Long]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: TestItemDetail => (this.fullyQualifiedName == x.fullyQualifiedName) && (this.status == x.status) && (this.duration == x.duration) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + fullyQualifiedName.##) + status.##) + duration.##) + } + override def toString: String = { + "TestItemDetail(" + fullyQualifiedName + ", " + status + ", " + duration + ")" + } + protected[this] def copy(fullyQualifiedName: String = fullyQualifiedName, status: sbt.testing.Status = status, duration: Option[Long] = duration): TestItemDetail = { + new TestItemDetail(fullyQualifiedName, status, duration) + } + def withFullyQualifiedName(fullyQualifiedName: String): TestItemDetail = { + copy(fullyQualifiedName = fullyQualifiedName) + } + def withStatus(status: sbt.testing.Status): TestItemDetail = { + copy(status = status) + } + def withDuration(duration: Option[Long]): TestItemDetail = { + copy(duration = duration) + } + def withDuration(duration: Long): TestItemDetail = { + copy(duration = Option(duration)) + } +} +object TestItemDetail { + + def apply(fullyQualifiedName: String, status: sbt.testing.Status, duration: Option[Long]): TestItemDetail = new TestItemDetail(fullyQualifiedName, status, duration) + def apply(fullyQualifiedName: String, status: sbt.testing.Status, duration: Long): TestItemDetail = new TestItemDetail(fullyQualifiedName, status, Option(duration)) +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala new file mode 100644 index 000000000..27139b070 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestItemEvent.scala @@ -0,0 +1,41 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Called for each test method or equivalent. */ +final class TestItemEvent private ( + val result: Option[sbt.protocol.testing.TestResult], + val detail: Vector[sbt.protocol.testing.TestItemDetail]) extends sbt.protocol.testing.TestMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: TestItemEvent => (this.result == x.result) && (this.detail == x.detail) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (17 + result.##) + detail.##) + } + override def toString: String = { + "TestItemEvent(" + result + ", " + detail + ")" + } + protected[this] def copy(result: Option[sbt.protocol.testing.TestResult] = result, detail: Vector[sbt.protocol.testing.TestItemDetail] = detail): TestItemEvent = { + new TestItemEvent(result, detail) + } + def withResult(result: Option[sbt.protocol.testing.TestResult]): TestItemEvent = { + copy(result = result) + } + def withResult(result: sbt.protocol.testing.TestResult): TestItemEvent = { + copy(result = Option(result)) + } + def withDetail(detail: Vector[sbt.protocol.testing.TestItemDetail]): TestItemEvent = { + copy(detail = detail) + } +} +object TestItemEvent { + + def apply(result: Option[sbt.protocol.testing.TestResult], detail: Vector[sbt.protocol.testing.TestItemDetail]): TestItemEvent = new TestItemEvent(result, detail) + def apply(result: sbt.protocol.testing.TestResult, detail: Vector[sbt.protocol.testing.TestItemDetail]): TestItemEvent = new TestItemEvent(Option(result), detail) +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala new file mode 100644 index 000000000..0a7360717 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestMessage.scala @@ -0,0 +1,26 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Events for testing */ +abstract class TestMessage() extends Serializable { + + + + +override def equals(o: Any): Boolean = o match { + case x: TestMessage => true + case _ => false +} +override def hashCode: Int = { + 17 +} +override def toString: String = { + "TestMessage()" +} +} +object TestMessage { + +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestResult.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestResult.scala new file mode 100644 index 000000000..d421e29ba --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestResult.scala @@ -0,0 +1,15 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +/** Testing result */ +sealed abstract class TestResult extends Serializable +object TestResult { + + + case object Passed extends TestResult + case object Failed extends TestResult + case object Error extends TestResult +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala new file mode 100644 index 000000000..ea1267c2f --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/TestStringEvent.scala @@ -0,0 +1,32 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing +final class TestStringEvent private ( + val value: String) extends sbt.protocol.testing.TestMessage() with Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: TestStringEvent => (this.value == x.value) + case _ => false + } + override def hashCode: Int = { + 37 * (17 + value.##) + } + override def toString: String = { + value + } + protected[this] def copy(value: String = value): TestStringEvent = { + new TestStringEvent(value) + } + def withValue(value: String): TestStringEvent = { + copy(value = value) + } +} +object TestStringEvent { + + def apply(value: String): TestStringEvent = new TestStringEvent(value) +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupErrorEventFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupErrorEventFormats.scala new file mode 100644 index 000000000..e978f6e39 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupErrorEventFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait EndTestGroupErrorEventFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val EndTestGroupErrorEventFormat: JsonFormat[sbt.protocol.testing.EndTestGroupErrorEvent] = new JsonFormat[sbt.protocol.testing.EndTestGroupErrorEvent] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.EndTestGroupErrorEvent = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val name = unbuilder.readField[String]("name") + val error = unbuilder.readField[String]("error") + unbuilder.endObject() + sbt.protocol.testing.EndTestGroupErrorEvent(name, error) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.EndTestGroupErrorEvent, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("name", obj.name) + builder.addField("error", obj.error) + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupEventFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupEventFormats.scala new file mode 100644 index 000000000..14767f783 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/EndTestGroupEventFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait EndTestGroupEventFormats { self: sbt.protocol.testing.codec.TestResultFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val EndTestGroupEventFormat: JsonFormat[sbt.protocol.testing.EndTestGroupEvent] = new JsonFormat[sbt.protocol.testing.EndTestGroupEvent] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.EndTestGroupEvent = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val name = unbuilder.readField[String]("name") + val result = unbuilder.readField[sbt.protocol.testing.TestResult]("result") + unbuilder.endObject() + sbt.protocol.testing.EndTestGroupEvent(name, result) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.EndTestGroupEvent, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("name", obj.name) + builder.addField("result", obj.result) + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/JsonProtocol.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/JsonProtocol.scala new file mode 100644 index 000000000..a9cdccd8d --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/JsonProtocol.scala @@ -0,0 +1,19 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +trait JsonProtocol extends sjsonnew.BasicJsonProtocol + with sbt.protocol.testing.codec.TestStringEventFormats + with sbt.protocol.testing.codec.TestInitEventFormats + with sbt.protocol.testing.codec.TestResultFormats + with sbt.protocol.testing.codec.TestCompleteEventFormats + with sbt.protocol.testing.codec.StartTestGroupEventFormats + with sbt.protocol.testing.codec.EndTestGroupEventFormats + with sbt.protocol.testing.codec.EndTestGroupErrorEventFormats + with sbt.internal.testing.StatusFormats + with sbt.protocol.testing.codec.TestItemDetailFormats + with sbt.protocol.testing.codec.TestItemEventFormats + with sbt.protocol.testing.codec.TestMessageFormats +object JsonProtocol extends JsonProtocol \ No newline at end of file diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/StartTestGroupEventFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/StartTestGroupEventFormats.scala new file mode 100644 index 000000000..2dc65ebd9 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/StartTestGroupEventFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait StartTestGroupEventFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val StartTestGroupEventFormat: JsonFormat[sbt.protocol.testing.StartTestGroupEvent] = new JsonFormat[sbt.protocol.testing.StartTestGroupEvent] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.StartTestGroupEvent = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val name = unbuilder.readField[String]("name") + unbuilder.endObject() + sbt.protocol.testing.StartTestGroupEvent(name) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.StartTestGroupEvent, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("name", obj.name) + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestCompleteEventFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestCompleteEventFormats.scala new file mode 100644 index 000000000..df6654faa --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestCompleteEventFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait TestCompleteEventFormats { self: sbt.protocol.testing.codec.TestResultFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val TestCompleteEventFormat: JsonFormat[sbt.protocol.testing.TestCompleteEvent] = new JsonFormat[sbt.protocol.testing.TestCompleteEvent] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.TestCompleteEvent = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val result = unbuilder.readField[sbt.protocol.testing.TestResult]("result") + unbuilder.endObject() + sbt.protocol.testing.TestCompleteEvent(result) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.TestCompleteEvent, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("result", obj.result) + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestInitEventFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestInitEventFormats.scala new file mode 100644 index 000000000..e721cdaba --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestInitEventFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait TestInitEventFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val TestInitEventFormat: JsonFormat[sbt.protocol.testing.TestInitEvent] = new JsonFormat[sbt.protocol.testing.TestInitEvent] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.TestInitEvent = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + + unbuilder.endObject() + sbt.protocol.testing.TestInitEvent() + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.TestInitEvent, builder: Builder[J]): Unit = { + builder.beginObject() + + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemDetailFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemDetailFormats.scala new file mode 100644 index 000000000..9c0a73eb8 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemDetailFormats.scala @@ -0,0 +1,31 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait TestItemDetailFormats { self: sbt.internal.testing.StatusFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val TestItemDetailFormat: JsonFormat[sbt.protocol.testing.TestItemDetail] = new JsonFormat[sbt.protocol.testing.TestItemDetail] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.TestItemDetail = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val fullyQualifiedName = unbuilder.readField[String]("fullyQualifiedName") + val status = unbuilder.readField[sbt.testing.Status]("status") + val duration = unbuilder.readField[Option[Long]]("duration") + unbuilder.endObject() + sbt.protocol.testing.TestItemDetail(fullyQualifiedName, status, duration) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.TestItemDetail, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("fullyQualifiedName", obj.fullyQualifiedName) + builder.addField("status", obj.status) + builder.addField("duration", obj.duration) + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemEventFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemEventFormats.scala new file mode 100644 index 000000000..c93fc1ee7 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestItemEventFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait TestItemEventFormats { self: sbt.protocol.testing.codec.TestResultFormats with sbt.protocol.testing.codec.TestItemDetailFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val TestItemEventFormat: JsonFormat[sbt.protocol.testing.TestItemEvent] = new JsonFormat[sbt.protocol.testing.TestItemEvent] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.TestItemEvent = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val result = unbuilder.readField[Option[sbt.protocol.testing.TestResult]]("result") + val detail = unbuilder.readField[Vector[sbt.protocol.testing.TestItemDetail]]("detail") + unbuilder.endObject() + sbt.protocol.testing.TestItemEvent(result, detail) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.TestItemEvent, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("result", obj.result) + builder.addField("detail", obj.detail) + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestMessageFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestMessageFormats.scala new file mode 100644 index 000000000..4b0c95d20 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestMessageFormats.scala @@ -0,0 +1,10 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait TestMessageFormats { self: sjsonnew.BasicJsonProtocol with sbt.protocol.testing.codec.TestStringEventFormats with sbt.protocol.testing.codec.TestInitEventFormats with sbt.protocol.testing.codec.TestResultFormats with sbt.protocol.testing.codec.TestCompleteEventFormats with sbt.protocol.testing.codec.StartTestGroupEventFormats with sbt.protocol.testing.codec.EndTestGroupEventFormats with sbt.protocol.testing.codec.EndTestGroupErrorEventFormats with sbt.protocol.testing.codec.TestItemDetailFormats with sbt.protocol.testing.codec.TestItemEventFormats => +implicit lazy val TestMessageFormat: JsonFormat[sbt.protocol.testing.TestMessage] = flatUnionFormat7[sbt.protocol.testing.TestMessage, sbt.protocol.testing.TestStringEvent, sbt.protocol.testing.TestInitEvent, sbt.protocol.testing.TestCompleteEvent, sbt.protocol.testing.StartTestGroupEvent, sbt.protocol.testing.EndTestGroupEvent, sbt.protocol.testing.EndTestGroupErrorEvent, sbt.protocol.testing.TestItemEvent]("type") +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestResultFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestResultFormats.scala new file mode 100644 index 000000000..dce5ec118 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestResultFormats.scala @@ -0,0 +1,31 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait TestResultFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val TestResultFormat: JsonFormat[sbt.protocol.testing.TestResult] = new JsonFormat[sbt.protocol.testing.TestResult] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.TestResult = { + jsOpt match { + case Some(js) => + unbuilder.readString(js) match { + case "Passed" => sbt.protocol.testing.TestResult.Passed + case "Failed" => sbt.protocol.testing.TestResult.Failed + case "Error" => sbt.protocol.testing.TestResult.Error + } + case None => + deserializationError("Expected JsString but found None") + } + } + override def write[J](obj: sbt.protocol.testing.TestResult, builder: Builder[J]): Unit = { + val str = obj match { + case sbt.protocol.testing.TestResult.Passed => "Passed" + case sbt.protocol.testing.TestResult.Failed => "Failed" + case sbt.protocol.testing.TestResult.Error => "Error" + } + builder.writeString(str) + } +} +} diff --git a/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestStringEventFormats.scala b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestStringEventFormats.scala new file mode 100644 index 000000000..fe33f9825 --- /dev/null +++ b/testing/src/main/contraband-scala/sbt/protocol/testing/codec/TestStringEventFormats.scala @@ -0,0 +1,27 @@ +/** + * This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.protocol.testing.codec +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } +trait TestStringEventFormats { self: sjsonnew.BasicJsonProtocol => +implicit lazy val TestStringEventFormat: JsonFormat[sbt.protocol.testing.TestStringEvent] = new JsonFormat[sbt.protocol.testing.TestStringEvent] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.protocol.testing.TestStringEvent = { + jsOpt match { + case Some(js) => + unbuilder.beginObject(js) + val value = unbuilder.readField[String]("value") + unbuilder.endObject() + sbt.protocol.testing.TestStringEvent(value) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.protocol.testing.TestStringEvent, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("value", obj.value) + builder.endObject() + } +} +} diff --git a/testing/src/main/contraband/testing.contra b/testing/src/main/contraband/testing.contra new file mode 100644 index 000000000..94cb92425 --- /dev/null +++ b/testing/src/main/contraband/testing.contra @@ -0,0 +1,65 @@ +package sbt.protocol.testing +@target(Scala) +@codecPackage("sbt.protocol.testing.codec") +@fullCodec("JsonProtocol") + +## Events for testing +interface TestMessage { +} + +type TestStringEvent implements TestMessage { + value: String! + #xtostring value +} + +## Called once, at beginning of the testing. +type TestInitEvent implements TestMessage {} + +## Called once, at end of the testing. +type TestCompleteEvent implements TestMessage { + result: sbt.protocol.testing.TestResult! +} + +## Called for each class or equivalent grouping. +type StartTestGroupEvent implements TestMessage { + name: String! +} + +## Called if test completed. +type EndTestGroupEvent implements TestMessage { + name: String! + result: sbt.protocol.testing.TestResult! +} + +## Called if test completed with an error. +type EndTestGroupErrorEvent implements TestMessage { + name: String! + error: String! +} + +## Called for each test method or equivalent. +type TestItemEvent implements TestMessage { + result: sbt.protocol.testing.TestResult + detail: [sbt.protocol.testing.TestItemDetail] +} + +## Mini version of sbt.testing.Event +type TestItemDetail { + ## The fully qualified name of a class that can rerun the suite or test + ## about which an event was fired. + fullyQualifiedName: String! + + ## Indicates whether the event represents a test success, failure, error, skipped, ignored, canceled, pending. + status: sbt.testing.Status! + + ## An amount of time, in milliseconds, that was required to complete the action reported by this event. + ## None, if no duration was available. + duration: Long +} + +## Testing result +enum TestResult { + Passed + Failed + Error +} diff --git a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala index ca0898ce7..0215c2fa9 100644 --- a/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala +++ b/testing/src/main/scala/sbt/JUnitXmlTestsListener.scala @@ -8,6 +8,7 @@ import scala.collection.mutable.ListBuffer import scala.util.DynamicVariable import scala.xml.{ Elem, Node => XNode, XML } import testing.{ Event => TEvent, Status => TStatus, OptionalThrowable, TestSelector } +import sbt.protocol.testing.TestResult /** * A tests listener that outputs the results it receives in junit xml @@ -161,7 +162,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { * Ends the current suite, wraps up the result and writes it to an XML file * in the output folder that is named after the suite. */ - override def endGroup(name: String, result: TestResult.Value) = { + override def endGroup(name: String, result: TestResult) = { writeSuite() } @@ -177,7 +178,7 @@ class JUnitXmlTestsListener(val outputDir: String) extends TestsListener { } /**Does nothing, as we write each file after a suite is done.*/ - override def doComplete(finalResult: TestResult.Value): Unit = {} + override def doComplete(finalResult: TestResult): Unit = {} /**Returns None*/ override def contentLogger(test: TestDefinition): Option[ContentLogger] = None diff --git a/testing/src/main/scala/sbt/TestFramework.scala b/testing/src/main/scala/sbt/TestFramework.scala index 9199f4188..ea2cd9ff1 100644 --- a/testing/src/main/scala/sbt/TestFramework.scala +++ b/testing/src/main/scala/sbt/TestFramework.scala @@ -10,12 +10,9 @@ import org.scalatools.testing.{ Framework => OldFramework } import sbt.internal.inc.classpath.{ ClasspathUtilities, DualLoader } import sbt.internal.inc.ScalaInstance import scala.annotation.tailrec -import sbt.util.Logger +import sbt.internal.util.ManagedLogger import sbt.io.IO - -object TestResult extends Enumeration { - val Passed, Failed, Error = Value -} +import sbt.protocol.testing.TestResult object TestFrameworks { val ScalaCheck = new TestFramework("org.scalacheck.ScalaCheckFramework") @@ -27,7 +24,7 @@ object TestFrameworks { case class TestFramework(implClassNames: String*) { @tailrec - private def createFramework(loader: ClassLoader, log: Logger, frameworkClassNames: List[String]): Option[Framework] = { + private def createFramework(loader: ClassLoader, log: ManagedLogger, frameworkClassNames: List[String]): Option[Framework] = { frameworkClassNames match { case head :: tail => try { @@ -45,7 +42,7 @@ case class TestFramework(implClassNames: String*) { } } - def create(loader: ClassLoader, log: Logger): Option[Framework] = + def create(loader: ClassLoader, log: ManagedLogger): Option[Framework] = createFramework(loader, log, implClassNames.toList) } final class TestDefinition(val name: String, val fingerprint: Fingerprint, val explicitlySpecified: Boolean, val selectors: Array[Selector]) { @@ -58,7 +55,7 @@ final class TestDefinition(val name: String, val fingerprint: Fingerprint, val e override def hashCode: Int = (name.hashCode, TestFramework.hashCode(fingerprint)).hashCode } -final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log: Logger) { +final class TestRunner(delegate: Runner, listeners: Seq[TestReportListener], log: ManagedLogger) { final def tasks(testDefs: Set[TestDefinition]): Array[TestTask] = delegate.tasks(testDefs.map(df => new TaskDef(df.name, df.fingerprint, df.explicitlySpecified, df.selectors)).toArray) @@ -106,7 +103,7 @@ object TestFramework { case _ => sys.error("Could not call 'fingerprints' on framework " + framework) } - private[sbt] def safeForeach[T](it: Iterable[T], log: Logger)(f: T => Unit): Unit = + private[sbt] def safeForeach[T](it: Iterable[T], log: ManagedLogger)(f: T => Unit): Unit = it.foreach(i => try f(i) catch { case NonFatal(e) => log.trace(e); log.error(e.toString) }) private[sbt] def hashCode(f: Fingerprint): Int = f match { @@ -132,9 +129,9 @@ object TestFramework { runners: Map[TestFramework, Runner], testLoader: ClassLoader, tests: Seq[TestDefinition], - log: Logger, + log: ManagedLogger, listeners: Seq[TestReportListener] - ): (() => Unit, Seq[(String, TestFunction)], TestResult.Value => () => Unit) = + ): (() => Unit, Seq[(String, TestFunction)], TestResult => () => Unit) = { val mappedTests = testMap(frameworks.values.toSeq, tests) if (mappedTests.isEmpty) @@ -160,7 +157,7 @@ object TestFramework { map.toMap.mapValues(_.toSet) } - private def createTestTasks(loader: ClassLoader, runners: Map[Framework, TestRunner], tests: Map[Framework, Set[TestDefinition]], ordered: Seq[TestDefinition], log: Logger, listeners: Seq[TestReportListener]) = + private def createTestTasks(loader: ClassLoader, runners: Map[Framework, TestRunner], tests: Map[Framework, Set[TestDefinition]], ordered: Seq[TestDefinition], log: ManagedLogger, listeners: Seq[TestReportListener]) = { val testsListeners = listeners collect { case tl: TestsListener => tl } @@ -178,7 +175,7 @@ object TestFramework { } } - val endTask = (result: TestResult.Value) => foreachListenerSafe(_.doComplete(result)) + val endTask = (result: TestResult) => foreachListenerSafe(_.doComplete(result)) (startTask, order(testTasks, ordered), endTask) } private[this] def withContextLoader[T](loader: ClassLoader)(eval: => T): T = diff --git a/testing/src/main/scala/sbt/TestReportListener.scala b/testing/src/main/scala/sbt/TestReportListener.scala index 878d9c765..9d4fcfe35 100644 --- a/testing/src/main/scala/sbt/TestReportListener.scala +++ b/testing/src/main/scala/sbt/TestReportListener.scala @@ -5,8 +5,7 @@ package sbt import testing.{ Logger => TLogger, Event => TEvent, Status => TStatus } -import sbt.internal.util.{ BufferedLogger, FullLogger } -import sbt.util.Level +import sbt.protocol.testing._ trait TestReportListener { /** called for each class or equivalent grouping */ @@ -19,23 +18,25 @@ trait TestReportListener { def endGroup(name: String, t: Throwable): Unit /** called if test completed */ - def endGroup(name: String, result: TestResult.Value): Unit + def endGroup(name: String, result: TestResult): Unit /** Used by the test framework for logging test results*/ def contentLogger(test: TestDefinition): Option[ContentLogger] = None } +final class ContentLogger(val log: TLogger, val flush: () => Unit) + trait TestsListener extends TestReportListener { /** called once, at beginning. */ def doInit(): Unit /** called once, at end. */ - def doComplete(finalResult: TestResult.Value): Unit + def doComplete(finalResult: TestResult): Unit } /** Provides the overall `result` of a group of tests (a suite) and test counts for each result type. */ final class SuiteResult( - val result: TestResult.Value, + val result: TestResult, val passedCount: Int, val failureCount: Int, val errorCount: Int, val skippedCount: Int, val ignoredCount: Int, val canceledCount: Int, val pendingCount: Int ) { @@ -65,7 +66,7 @@ object SuiteResult { } abstract class TestEvent { - def result: Option[TestResult.Value] + def result: Option[TestResult] def detail: Seq[TEvent] = Nil } object TestEvent { @@ -75,8 +76,8 @@ object TestEvent { override val detail = events } - private[sbt] def overallResult(events: Seq[TEvent]): TestResult.Value = - (TestResult.Passed /: events) { (sum, event) => + private[sbt] def overallResult(events: Seq[TEvent]): TestResult = + ((TestResult.Passed: TestResult) /: events) { (sum, event) => (sum, event.status) match { case (TestResult.Error, _) => TestResult.Error case (_, TStatus.Error) => TestResult.Error @@ -86,60 +87,3 @@ object TestEvent { } } } - -object TestLogger { - @deprecated("Doesn't provide for underlying resources to be released.", "0.13.1") - def apply(logger: sbt.util.Logger, logTest: TestDefinition => sbt.util.Logger, buffered: Boolean): TestLogger = - new TestLogger(new TestLogging(wrap(logger), tdef => contentLogger(logTest(tdef), buffered))) - - @deprecated("Doesn't provide for underlying resources to be released.", "0.13.1") - def contentLogger(log: sbt.util.Logger, buffered: Boolean): ContentLogger = - { - val blog = new BufferedLogger(FullLogger(log)) - if (buffered) blog.record() - new ContentLogger(wrap(blog), () => blog.stopQuietly()) - } - - final class PerTest private[sbt] (val log: sbt.util.Logger, val flush: () => Unit, val buffered: Boolean) - - def make(global: sbt.util.Logger, perTest: TestDefinition => PerTest): TestLogger = - { - def makePerTest(tdef: TestDefinition): ContentLogger = - { - val per = perTest(tdef) - val blog = new BufferedLogger(FullLogger(per.log)) - if (per.buffered) blog.record() - new ContentLogger(wrap(blog), () => { blog.stopQuietly(); per.flush() }) - } - val config = new TestLogging(wrap(global), makePerTest) - new TestLogger(config) - } - - def wrap(logger: sbt.util.Logger): TLogger = - new TLogger { - def error(s: String) = log(Level.Error, s) - def warn(s: String) = log(Level.Warn, s) - def info(s: String) = log(Level.Info, s) - def debug(s: String) = log(Level.Debug, s) - def trace(t: Throwable) = logger.trace(t) - private def log(level: Level.Value, s: String) = logger.log(level, s) - def ansiCodesSupported() = logger.ansiCodesSupported - } -} -final class TestLogging(val global: TLogger, val logTest: TestDefinition => ContentLogger) -final class ContentLogger(val log: TLogger, val flush: () => Unit) -class TestLogger(val logging: TestLogging) extends TestsListener { - import logging.{ global => log, logTest } - - def startGroup(name: String): Unit = () - def testEvent(event: TestEvent): Unit = () - def endGroup(name: String, t: Throwable): Unit = { - log.trace(t) - log.error("Could not run test " + name + ": " + t.toString) - } - def endGroup(name: String, result: TestResult.Value): Unit = () - def doInit: Unit = () - /** called once, at end of test group. */ - def doComplete(finalResult: TestResult.Value): Unit = () - override def contentLogger(test: TestDefinition): Option[ContentLogger] = Some(logTest(test)) -} diff --git a/testing/src/main/scala/sbt/TestStatusReporter.scala b/testing/src/main/scala/sbt/TestStatusReporter.scala index 738b6c6cf..90a1a40f5 100644 --- a/testing/src/main/scala/sbt/TestStatusReporter.scala +++ b/testing/src/main/scala/sbt/TestStatusReporter.scala @@ -7,6 +7,7 @@ import java.io.File import sbt.io.IO import scala.collection.mutable.Map +import sbt.protocol.testing.TestResult // Assumes exclusive ownership of the file. private[sbt] class TestStatusReporter(f: File) extends TestsListener { @@ -16,11 +17,11 @@ private[sbt] class TestStatusReporter(f: File) extends TestsListener { def startGroup(name: String): Unit = { succeeded remove name } def testEvent(event: TestEvent): Unit = () def endGroup(name: String, t: Throwable): Unit = () - def endGroup(name: String, result: TestResult.Value): Unit = { + def endGroup(name: String, result: TestResult): Unit = { if (result == TestResult.Passed) succeeded(name) = System.currentTimeMillis } - def doComplete(finalResult: TestResult.Value): Unit = { + def doComplete(finalResult: TestResult): Unit = { TestStatus.write(succeeded, "Successful Tests", f) } } diff --git a/testing/src/main/scala/sbt/internal/testing/StatusFormats.scala b/testing/src/main/scala/sbt/internal/testing/StatusFormats.scala new file mode 100644 index 000000000..862204488 --- /dev/null +++ b/testing/src/main/scala/sbt/internal/testing/StatusFormats.scala @@ -0,0 +1,38 @@ +package sbt.internal.testing + +import sbt.testing.Status + +import _root_.sjsonnew.{ deserializationError, serializationError, Builder, JsonFormat, Unbuilder } + +trait StatusFormats { self: sjsonnew.BasicJsonProtocol => + implicit lazy val StatusFormat: JsonFormat[Status] = new JsonFormat[Status] { + override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): Status = { + jsOpt match { + case Some(js) => + unbuilder.readString(js) match { + case "Success" => Status.Success + case "Error" => Status.Error + case "Failure" => Status.Failure + case "Skipped" => Status.Skipped + case "Ignored" => Status.Ignored + case "Canceled" => Status.Canceled + case "Pending" => Status.Pending + } + case None => + deserializationError("Expected JsString but found None") + } + } + override def write[J](obj: Status, builder: Builder[J]): Unit = { + val str = obj match { + case Status.Success => "Success" + case Status.Error => "Error" + case Status.Failure => "Failure" + case Status.Skipped => "Skipped" + case Status.Ignored => "Ignored" + case Status.Canceled => "Canceled" + case Status.Pending => "Pending" + } + builder.writeString(str) + } + } +} diff --git a/testing/src/main/scala/sbt/internal/testing/TestLogger.scala b/testing/src/main/scala/sbt/internal/testing/TestLogger.scala new file mode 100644 index 000000000..3bd75c728 --- /dev/null +++ b/testing/src/main/scala/sbt/internal/testing/TestLogger.scala @@ -0,0 +1,101 @@ +package sbt +package internal.testing + +import testing.{ Logger => TLogger } +import sbt.internal.util.{ ManagedLogger, BufferedAppender } +import sbt.util.{ Level, LogExchange } +import sbt.protocol.testing._ +import java.util.concurrent.atomic.AtomicInteger +import scala.collection.JavaConverters._ + +object TestLogger { + import sbt.protocol.testing.codec.JsonProtocol._ + + private def generateName: String = + "test-" + generateId.incrementAndGet + private val generateId: AtomicInteger = new AtomicInteger + private def generateBufferName: String = + "testbuffer-" + generateBufferId.incrementAndGet + private val generateBufferId: AtomicInteger = new AtomicInteger + + final class PerTest private[sbt] (val log: ManagedLogger, val flush: () => Unit, val buffered: Boolean) + + def make(global: ManagedLogger, perTest: TestDefinition => PerTest): TestLogger = + { + def makePerTest(tdef: TestDefinition): ContentLogger = + { + val per = perTest(tdef) + val l0 = per.log + val config = LogExchange.loggerConfig(l0.name) + val as = config.getAppenders.asScala + val buffs: List[BufferedAppender] = (as map { + case (k, v) => BufferedAppender(generateBufferName, v) + }).toList + val newLog = LogExchange.logger(generateName, l0.channelName, l0.execId) + LogExchange.bindLoggerAppenders(newLog.name, buffs map { x => (x, Level.Debug) }) + if (per.buffered) { + buffs foreach { _.record() } + } + new ContentLogger(wrap(newLog), () => { + buffs foreach { _.stopQuietly() } + per.flush() + }) + } + val config = new TestLogging(wrap(global), global, makePerTest) + new TestLogger(config) + } + + def wrap(logger: ManagedLogger): TLogger = + new TLogger { + def error(s: String) = log(Level.Error, TestStringEvent(s)) + def warn(s: String) = log(Level.Warn, TestStringEvent(s)) + def info(s: String) = log(Level.Info, TestStringEvent(s)) + def debug(s: String) = log(Level.Debug, TestStringEvent(s)) + def trace(t: Throwable) = logger.trace(t) + private def log(level: Level.Value, event: TestStringEvent) = logger.logEvent(level, event) + def ansiCodesSupported() = logger.ansiCodesSupported + } + + private[sbt] def toTestItemEvent(event: TestEvent): TestItemEvent = + TestItemEvent(event.result, event.detail.toVector map { d => + TestItemDetail( + d.fullyQualifiedName, + d.status, + d.duration match { + case -1 => None + case x => Some(x) + } + ) + }) +} +final class TestLogging( + val global: TLogger, + val managed: ManagedLogger, + val logTest: TestDefinition => ContentLogger +) + +class TestLogger(val logging: TestLogging) extends TestsListener { + import TestLogger._ + import logging.{ global => log, logTest, managed } + import sbt.protocol.testing.codec.JsonProtocol._ + + def startGroup(name: String): Unit = + managed.logEvent(Level.Debug, StartTestGroupEvent(name)) + def testEvent(event: TestEvent): Unit = + managed.logEvent(Level.Debug, toTestItemEvent(event)) + def endGroup(name: String, t: Throwable): Unit = { + log.trace(t) + log.error("Could not run test " + name + ": " + t.toString) + managed.logEvent(Level.Debug, EndTestGroupErrorEvent( + name, + t.getMessage + "\n" + t.getStackTrace.toList.mkString("\n") + )) + } + def endGroup(name: String, result: TestResult): Unit = + managed.logEvent(Level.Debug, EndTestGroupEvent(name, result)) + def doInit: Unit = managed.logEvent(Level.Debug, TestInitEvent()) + /** called once, at end of test group. */ + def doComplete(finalResult: TestResult): Unit = + managed.logEvent(Level.Debug, TestCompleteEvent(finalResult)) + override def contentLogger(test: TestDefinition): Option[ContentLogger] = Some(logTest(test)) +}