diff --git a/main/src/main/scala/sbt/Keys.scala b/main/src/main/scala/sbt/Keys.scala index ebf62d8f8..1b22f3188 100644 --- a/main/src/main/scala/sbt/Keys.scala +++ b/main/src/main/scala/sbt/Keys.scala @@ -401,6 +401,8 @@ object Keys { val bspBuildTargetRun = inputKey[Unit]("Corresponds to buildTarget/run request").withRank(DTask) val bspBuildTargetScalacOptions = inputKey[Unit]("").withRank(DTask) val bspBuildTargetScalacOptionsItem = taskKey[ScalacOptionsItem]("").withRank(DTask) + val bspScalaTestClasses = inputKey[Unit]("Corresponds to buildTarget/scalaTestClasses request").withRank(DTask) + val bspScalaTestClassesItem = taskKey[ScalaTestClassesItem]("").withRank(DTask) val bspScalaMainClasses = inputKey[Unit]("Corresponds to buildTarget/scalaMainClasses request").withRank(DTask) val bspScalaMainClassesItem = taskKey[ScalaMainClassesItem]("").withRank(DTask) diff --git a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala index 4065d760c..b43157e56 100644 --- a/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala +++ b/main/src/main/scala/sbt/internal/server/BuildServerProtocol.scala @@ -142,6 +142,17 @@ object BuildServerProtocol { } }.evaluated, bspBuildTargetScalacOptions / aggregate := false, + bspScalaTestClasses := Def.inputTaskDyn { + val s = state.value + val workspace = bspWorkspace.value + val targets = spaceDelimited().parsed.map(uri => BuildTargetIdentifier(URI.create(uri))) + val filter = ScopeFilter.in(targets.map(workspace)) + Def.task { + val items = bspScalaTestClassesItem.all(filter).value + val result = ScalaTestClassesResult(items.toVector, None) + s.respondEvent(result) + } + }.evaluated, bspScalaMainClasses := Def.inputTaskDyn { val s = state.value val workspace = bspWorkspace.value @@ -152,7 +163,8 @@ object BuildServerProtocol { val result = ScalaMainClassesResult(items.toVector, None) s.respondEvent(result) } - }.evaluated + }.evaluated, + bspScalaMainClasses / aggregate := false ) // This will be scoped to Compile, Test, IntegrationTest etc @@ -180,6 +192,7 @@ object BuildServerProtocol { bspBuildTargetRun := bspRunTask.evaluated, bspBuildTargetScalacOptionsItem := scalacOptionsTask.value, bspInternalDependencyConfigurations := internalDependencyConfigurationsSetting.value, + bspScalaTestClassesItem := scalaTestClassesTask.value, bspScalaMainClassesItem := scalaMainClassesTask.value ) @@ -267,6 +280,12 @@ object BuildServerProtocol { val command = Keys.bspBuildTargetScalacOptions.key val _ = callback.appendExec(s"$command $targets", Some(r.id)) + case r: JsonRpcRequestMessage if r.method == "buildTarget/scalaTestClasses" => + val param = Converter.fromJson[ScalaTestClassesParams](json(r)).get + val targets = param.targets.map(_.uri).mkString(" ") + val command = Keys.bspScalaTestClasses.key + val _ = callback.appendExec(s"$command $targets", Some(r.id)) + case r: JsonRpcRequestMessage if r.method == "buildTarget/scalaMainClasses" => val param = Converter.fromJson[ScalaMainClassesParams](json(r)).get val targets = param.targets.map(_.uri).mkString(" ") @@ -524,6 +543,17 @@ object BuildServerProtocol { } } + private def scalaTestClassesTask: Initialize[Task[ScalaTestClassesItem]] = Def.task { + val testClasses = Keys.definedTests.?.value + .getOrElse(Seq.empty) + .map(_.name) + .toVector + ScalaTestClassesItem( + bspTargetIdentifier.value, + testClasses + ) + } + private def scalaMainClassesTask: Initialize[Task[ScalaMainClassesItem]] = Def.task { val jvmOptions = Keys.javaOptions.value.toVector val mainClasses = Keys.discoveredMainClasses.value.map( diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/RunResult.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/RunResult.scala index 33583b0ed..89f807cd3 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/RunResult.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/RunResult.scala @@ -7,7 +7,7 @@ package sbt.internal.bsp /** * Run Result * @param originId An optional request id to know the origin of this report. - * @param statusCode A status code fore the execution. + * @param statusCode A status code for the execution. */ final class RunResult private ( val originId: Option[String], diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaMainClassesParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaMainClassesParams.scala index 766cfed1d..3a3c10173 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaMainClassesParams.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaMainClassesParams.scala @@ -5,6 +5,7 @@ // DO NOT EDIT MANUALLY package sbt.internal.bsp /** + * Scala Main Class Request * The build target main classes request is sent from the client to the server * to query for the list of main classes that can be fed as arguments to buildTarget/run. * @param originId An optional number uniquely identifying a client request. diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesItem.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesItem.scala new file mode 100644 index 000000000..087404198 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesItem.scala @@ -0,0 +1,40 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * @param target The build target that contains the test classes. + * @param classes The fully qualified names of the test classes in this target + */ +final class ScalaTestClassesItem private ( + val target: sbt.internal.bsp.BuildTargetIdentifier, + val classes: Vector[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ScalaTestClassesItem => (this.target == x.target) && (this.classes == x.classes) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalaTestClassesItem".##) + target.##) + classes.##) + } + override def toString: String = { + "ScalaTestClassesItem(" + target + ", " + classes + ")" + } + private[this] def copy(target: sbt.internal.bsp.BuildTargetIdentifier = target, classes: Vector[String] = classes): ScalaTestClassesItem = { + new ScalaTestClassesItem(target, classes) + } + def withTarget(target: sbt.internal.bsp.BuildTargetIdentifier): ScalaTestClassesItem = { + copy(target = target) + } + def withClasses(classes: Vector[String]): ScalaTestClassesItem = { + copy(classes = classes) + } +} +object ScalaTestClassesItem { + + def apply(target: sbt.internal.bsp.BuildTargetIdentifier, classes: Vector[String]): ScalaTestClassesItem = new ScalaTestClassesItem(target, classes) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesParams.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesParams.scala new file mode 100644 index 000000000..7903fba21 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesParams.scala @@ -0,0 +1,46 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** + * Scala Test Class Request + * The build target scala test options request is sent from the client to the server + * to query for the list of fully qualified names of test clases in a given list of targets. + * @param originId An optional number uniquely identifying a client request. + */ +final class ScalaTestClassesParams private ( + val targets: Vector[sbt.internal.bsp.BuildTargetIdentifier], + val originId: Option[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ScalaTestClassesParams => (this.targets == x.targets) && (this.originId == x.originId) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalaTestClassesParams".##) + targets.##) + originId.##) + } + override def toString: String = { + "ScalaTestClassesParams(" + targets + ", " + originId + ")" + } + private[this] def copy(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier] = targets, originId: Option[String] = originId): ScalaTestClassesParams = { + new ScalaTestClassesParams(targets, originId) + } + def withTargets(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier]): ScalaTestClassesParams = { + copy(targets = targets) + } + def withOriginId(originId: Option[String]): ScalaTestClassesParams = { + copy(originId = originId) + } + def withOriginId(originId: String): ScalaTestClassesParams = { + copy(originId = Option(originId)) + } +} +object ScalaTestClassesParams { + + def apply(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier], originId: Option[String]): ScalaTestClassesParams = new ScalaTestClassesParams(targets, originId) + def apply(targets: Vector[sbt.internal.bsp.BuildTargetIdentifier], originId: String): ScalaTestClassesParams = new ScalaTestClassesParams(targets, Option(originId)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesResult.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesResult.scala new file mode 100644 index 000000000..8c84ec00e --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/ScalaTestClassesResult.scala @@ -0,0 +1,41 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp +/** @param originId An optional id of the request that triggered this result. */ +final class ScalaTestClassesResult private ( + val items: Vector[sbt.internal.bsp.ScalaTestClassesItem], + val originId: Option[String]) extends Serializable { + + + + override def equals(o: Any): Boolean = o match { + case x: ScalaTestClassesResult => (this.items == x.items) && (this.originId == x.originId) + case _ => false + } + override def hashCode: Int = { + 37 * (37 * (37 * (17 + "sbt.internal.bsp.ScalaTestClassesResult".##) + items.##) + originId.##) + } + override def toString: String = { + "ScalaTestClassesResult(" + items + ", " + originId + ")" + } + private[this] def copy(items: Vector[sbt.internal.bsp.ScalaTestClassesItem] = items, originId: Option[String] = originId): ScalaTestClassesResult = { + new ScalaTestClassesResult(items, originId) + } + def withItems(items: Vector[sbt.internal.bsp.ScalaTestClassesItem]): ScalaTestClassesResult = { + copy(items = items) + } + def withOriginId(originId: Option[String]): ScalaTestClassesResult = { + copy(originId = originId) + } + def withOriginId(originId: String): ScalaTestClassesResult = { + copy(originId = Option(originId)) + } +} +object ScalaTestClassesResult { + + def apply(items: Vector[sbt.internal.bsp.ScalaTestClassesItem], originId: Option[String]): ScalaTestClassesResult = new ScalaTestClassesResult(items, originId) + def apply(items: Vector[sbt.internal.bsp.ScalaTestClassesItem], originId: String): ScalaTestClassesResult = new ScalaTestClassesResult(items, Option(originId)) +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala index 0cd9acee0..0a2e3fffa 100644 --- a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/JsonProtocol.scala @@ -44,6 +44,9 @@ trait JsonProtocol extends sjsonnew.BasicJsonProtocol with sbt.internal.bsp.codec.ScalacOptionsResultFormats with sbt.internal.bsp.codec.BspConnectionDetailsFormats with sbt.internal.bsp.codec.MetalsMetadataFormats + with sbt.internal.bsp.codec.ScalaTestClassesParamsFormats + with sbt.internal.bsp.codec.ScalaTestClassesItemFormats + with sbt.internal.bsp.codec.ScalaTestClassesResultFormats with sbt.internal.bsp.codec.ScalaMainClassesParamsFormats with sbt.internal.bsp.codec.ScalaMainClassFormats with sbt.internal.bsp.codec.ScalaMainClassesItemFormats diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesItemFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesItemFormats.scala new file mode 100644 index 000000000..a079301b3 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesItemFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait ScalaTestClassesItemFormats { self: sbt.internal.bsp.codec.BuildTargetIdentifierFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val ScalaTestClassesItemFormat: JsonFormat[sbt.internal.bsp.ScalaTestClassesItem] = new JsonFormat[sbt.internal.bsp.ScalaTestClassesItem] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalaTestClassesItem = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val target = unbuilder.readField[sbt.internal.bsp.BuildTargetIdentifier]("target") + val classes = unbuilder.readField[Vector[String]]("classes") + unbuilder.endObject() + sbt.internal.bsp.ScalaTestClassesItem(target, classes) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.ScalaTestClassesItem, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("target", obj.target) + builder.addField("classes", obj.classes) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesParamsFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesParamsFormats.scala new file mode 100644 index 000000000..8a3cad4ae --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesParamsFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait ScalaTestClassesParamsFormats { self: sbt.internal.bsp.codec.BuildTargetIdentifierFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val ScalaTestClassesParamsFormat: JsonFormat[sbt.internal.bsp.ScalaTestClassesParams] = new JsonFormat[sbt.internal.bsp.ScalaTestClassesParams] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalaTestClassesParams = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val targets = unbuilder.readField[Vector[sbt.internal.bsp.BuildTargetIdentifier]]("targets") + val originId = unbuilder.readField[Option[String]]("originId") + unbuilder.endObject() + sbt.internal.bsp.ScalaTestClassesParams(targets, originId) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.ScalaTestClassesParams, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("targets", obj.targets) + builder.addField("originId", obj.originId) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesResultFormats.scala b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesResultFormats.scala new file mode 100644 index 000000000..ab51a7bb2 --- /dev/null +++ b/protocol/src/main/contraband-scala/sbt/internal/bsp/codec/ScalaTestClassesResultFormats.scala @@ -0,0 +1,29 @@ +/** + * This code is generated using [[https://www.scala-sbt.org/contraband/ sbt-contraband]]. + */ + +// DO NOT EDIT MANUALLY +package sbt.internal.bsp.codec +import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError } +trait ScalaTestClassesResultFormats { self: sbt.internal.bsp.codec.ScalaTestClassesItemFormats with sjsonnew.BasicJsonProtocol => +implicit lazy val ScalaTestClassesResultFormat: JsonFormat[sbt.internal.bsp.ScalaTestClassesResult] = new JsonFormat[sbt.internal.bsp.ScalaTestClassesResult] { + override def read[J](__jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.internal.bsp.ScalaTestClassesResult = { + __jsOpt match { + case Some(__js) => + unbuilder.beginObject(__js) + val items = unbuilder.readField[Vector[sbt.internal.bsp.ScalaTestClassesItem]]("items") + val originId = unbuilder.readField[Option[String]]("originId") + unbuilder.endObject() + sbt.internal.bsp.ScalaTestClassesResult(items, originId) + case None => + deserializationError("Expected JsObject but found None") + } + } + override def write[J](obj: sbt.internal.bsp.ScalaTestClassesResult, builder: Builder[J]): Unit = { + builder.beginObject() + builder.addField("items", obj.items) + builder.addField("originId", obj.originId) + builder.endObject() + } +} +} diff --git a/protocol/src/main/contraband/bsp.contra b/protocol/src/main/contraband/bsp.contra index c4b184e27..ca7031cd7 100644 --- a/protocol/src/main/contraband/bsp.contra +++ b/protocol/src/main/contraband/bsp.contra @@ -371,6 +371,9 @@ type CompileReport { time: Int } + + + ## Run Request ## The run request is sent from the client to the server to run a build target. ## The server communicates during the initialize handshake whether this method is supported or not. @@ -399,7 +402,7 @@ type RunResult { ## An optional request id to know the origin of this report. originId: String - ## A status code fore the execution. + ## A status code for the execution. statusCode: Int! } @@ -507,6 +510,32 @@ type MetalsMetadata { supportedScalaVersions: [String] } +## Scala Test Class Request +## The build target scala test options request is sent from the client to the server +## to query for the list of fully qualified names of test clases in a given list of targets. +type ScalaTestClassesParams { + targets: [sbt.internal.bsp.BuildTargetIdentifier] + + ## An optional number uniquely identifying a client request. + originId: String +} + +type ScalaTestClassesResult { + items: [sbt.internal.bsp.ScalaTestClassesItem] + + ## An optional id of the request that triggered this result. + originId: String +} + +type ScalaTestClassesItem { + ## The build target that contains the test classes. + target: sbt.internal.bsp.BuildTargetIdentifier! + + ## The fully qualified names of the test classes in this target + classes: [String] +} + +## Scala Main Class Request ## The build target main classes request is sent from the client to the server ## to query for the list of main classes that can be fed as arguments to buildTarget/run. type ScalaMainClassesParams { diff --git a/server-test/src/server-test/buildserver/build.sbt b/server-test/src/server-test/buildserver/build.sbt index 2bc71ff1e..fbbfc556b 100644 --- a/server-test/src/server-test/buildserver/build.sbt +++ b/server-test/src/server-test/buildserver/build.sbt @@ -6,6 +6,9 @@ lazy val root = (project in file(".")) .aggregate(foo, util) lazy val foo = project.in(file("foo")) + .settings( + libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test", + ) .dependsOn(util) lazy val util = project diff --git a/server-test/src/server-test/buildserver/foo/src/main/scala/foo/FooMain.scala b/server-test/src/server-test/buildserver/foo/src/main/scala/foo/FooMain.scala index 9c5a63b10..29209deca 100644 --- a/server-test/src/server-test/buildserver/foo/src/main/scala/foo/FooMain.scala +++ b/server-test/src/server-test/buildserver/foo/src/main/scala/foo/FooMain.scala @@ -1,5 +1,7 @@ package foo object FooMain extends App { - println("Hello World!") + val message = "Hello World!" + + println(message) } diff --git a/server-test/src/server-test/buildserver/foo/src/test/scala/foo/FooTest.scala b/server-test/src/server-test/buildserver/foo/src/test/scala/foo/FooTest.scala new file mode 100644 index 000000000..8e230be99 --- /dev/null +++ b/server-test/src/server-test/buildserver/foo/src/test/scala/foo/FooTest.scala @@ -0,0 +1,9 @@ +package foo + +import org.scalatest.FreeSpec + +class FooTest extends FreeSpec { + "test message" in { + assert(FooMain.message == "Hello World!") + } +} \ No newline at end of file diff --git a/server-test/src/test/scala/testpkg/BuildServerTest.scala b/server-test/src/test/scala/testpkg/BuildServerTest.scala index 1bef9cd82..6ee1a26e6 100644 --- a/server-test/src/test/scala/testpkg/BuildServerTest.scala +++ b/server-test/src/test/scala/testpkg/BuildServerTest.scala @@ -120,6 +120,20 @@ object BuildServerTest extends AbstractServerTest { }) } + test("buildTarget/scalaTestClasses") { _ => + val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#foo/Test" + svr.sendJsonRpc( + s"""{ "jsonrpc": "2.0", "id": "18", "method": "buildTarget/scalaTestClasses", "params": { + | "targets": [{ "uri": "$x" }] + |} }""".stripMargin + ) + assert(svr.waitForString(10.seconds) { s => + println(s) + (s contains """"id":"18"""") && + (s contains """"classes":["foo.FooTest"]""") + }) + } + def initializeRequest(): Unit = { svr.sendJsonRpc( """{ "jsonrpc": "2.0", "id": "10", "method": "build/initialize",