Merge pull request #6566 from adpi2/fix-6010

[BSP] Send diagnostics and meaningful error message when reloading fails
This commit is contained in:
eugene yokota 2021-07-12 16:01:48 -04:00 committed by GitHub
commit d4162cce04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 341 additions and 106 deletions

View File

@ -681,6 +681,8 @@ lazy val actionsProj = (project in file("main-actions"))
exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.filesModifiedBytes"),
exclude[DirectMissingMethodProblem]("sbt.compiler.Eval.fileModifiedBytes"),
exclude[DirectMissingMethodProblem]("sbt.Doc.$init$"),
// Added field in nested private[this] class
exclude[ReversedMissingMethodProblem]("sbt.compiler.Eval#EvalType.sourceName"),
),
)
.configure(

View File

@ -12,7 +12,7 @@ import scala.collection.mutable.ListBuffer
import scala.tools.nsc.{ ast, io, reporters, CompilerCommand, Global, Phase, Settings }
import io.{ AbstractFile, PlainFile, VirtualDirectory }
import ast.parser.Tokens
import reporters.{ ConsoleReporter, Reporter }
import reporters.Reporter
import scala.reflect.internal.util.{ AbstractFileClassLoader, BatchSourceFile }
import Tokens.{ EOF, NEWLINE, NEWLINES, SEMI }
import java.io.{ File, FileNotFoundException }
@ -65,12 +65,12 @@ final class EvalException(msg: String) extends RuntimeException(msg)
final class Eval(
optionsNoncp: Seq[String],
classpath: Seq[File],
mkReporter: Settings => Reporter,
mkReporter: Settings => EvalReporter,
backing: Option[File]
) {
def this(mkReporter: Settings => Reporter, backing: Option[File]) =
def this(mkReporter: Settings => EvalReporter, backing: Option[File]) =
this(Nil, IO.classLocationPath[Product].toFile :: Nil, mkReporter, backing)
def this() = this(s => new ConsoleReporter(s), None)
def this() = this(EvalReporter.console, None)
backing.foreach(IO.createDirectory)
val classpathString = Path.makeString(classpath ++ backing.toList)
@ -81,8 +81,8 @@ final class Eval(
new CompilerCommand(options.toList, s) // this side-effects on Settings..
s
}
lazy val reporter = mkReporter(settings)
private lazy val evalReporter = mkReporter(settings)
def reporter: Reporter = evalReporter // kept for binary compatibility
/**
* Subclass of Global which allows us to mutate currentRun from outside.
* See for rationale https://issues.scala-lang.org/browse/SI-8794
@ -95,7 +95,7 @@ final class Eval(
}
var curRun: Run = null
}
lazy val global: EvalGlobal = new EvalGlobal(settings, reporter)
lazy val global: EvalGlobal = new EvalGlobal(settings, evalReporter)
import global._
private[sbt] def unlinkDeferred(): Unit = {
@ -114,6 +114,7 @@ final class Eval(
line: Int = DefaultStartLine
): EvalResult = {
val ev = new EvalType[String] {
def sourceName: String = srcName
def makeUnit = mkUnit(srcName, line, expression)
def unlink = true
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
@ -142,6 +143,7 @@ final class Eval(
require(definitions.nonEmpty, "Definitions to evaluate cannot be empty.")
val ev = new EvalType[Seq[String]] {
lazy val (fullUnit, defUnits) = mkDefsUnit(srcName, definitions)
def sourceName: String = srcName
def makeUnit = fullUnit
def unlink = false
def unitBody(unit: CompilationUnit, importTrees: Seq[Tree], moduleName: String): Tree = {
@ -202,28 +204,19 @@ final class Eval(
val hash = Hash.toHex(d)
val moduleName = makeModuleName(hash)
lazy val unit = {
reporter.reset
ev.makeUnit
}
lazy val run = new Run {
override def units = (unit :: Nil).iterator
}
def unlinkAll(): Unit =
for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym
val (extra, loader) = backing match {
case Some(back) if classExists(back, moduleName) =>
val loader = (parent: ClassLoader) =>
(new URLClassLoader(Array(back.toURI.toURL), parent): ClassLoader)
val extra = ev.read(cacheFile(back, moduleName))
(extra, loader)
case _ =>
try {
compileAndLoad(run, unit, imports, backing, moduleName, ev)
} finally {
unlinkAll()
}
val (extra, loader) = try {
backing match {
case Some(back) if classExists(back, moduleName) =>
val loader = (parent: ClassLoader) =>
(new URLClassLoader(Array(back.toURI.toURL), parent): ClassLoader)
val extra = ev.read(cacheFile(back, moduleName))
(extra, loader)
case _ =>
compileAndLoad(imports, backing, moduleName, ev)
}
} finally {
// send a final report even if the class file was backed to reset preceding diagnostics
evalReporter.finalReport(ev.sourceName)
}
val generatedFiles = getGeneratedFiles(backing, moduleName)
@ -232,6 +225,25 @@ final class Eval(
// location of the cached type or definition information
private[this] def cacheFile(base: File, moduleName: String): File =
new File(base, moduleName + ".cache")
private def compileAndLoad[T](
imports: EvalImports,
backing: Option[File],
moduleName: String,
ev: EvalType[T]
): (T, ClassLoader => ClassLoader) = {
evalReporter.reset()
val unit = ev.makeUnit
val run = new Run {
override def units = (unit :: Nil).iterator
}
try {
compileAndLoad(run, unit, imports, backing, moduleName, ev)
} finally {
// unlink all
for ((sym, _) <- run.symSource) if (ev.unlink) unlink(sym) else toUnlinkLater ::= sym
}
}
private[this] def compileAndLoad[T](
run: Run,
unit: CompilationUnit,
@ -250,7 +262,7 @@ final class Eval(
def compile(phase: Phase): Unit = {
globalPhase = phase
if (phase == null || phase == phase.next || reporter.hasErrors)
if (phase == null || phase == phase.next || evalReporter.hasErrors)
()
else {
enteringPhase(phase) { phase.run }
@ -457,6 +469,8 @@ final class Eval(
/** Serializes the extra information to a cache file, where it can be `read` back if inputs haven't changed.*/
def write(value: T, file: File): Unit
def sourceName: String
/**
* Constructs the full compilation unit for this evaluation.
* This is used for error reporting during compilation.
@ -484,7 +498,7 @@ final class Eval(
private[this] def mkUnit(srcName: String, firstLine: Int, s: String) =
new CompilationUnit(new EvalSourceFile(srcName, firstLine, s))
private[this] def checkError(label: String) =
if (reporter.hasErrors) throw new EvalException(label)
if (evalReporter.hasErrors) throw new EvalException(label)
private[this] final class EvalSourceFile(name: String, startLine: Int, contents: String)
extends BatchSourceFile(name, contents) {

View File

@ -0,0 +1,62 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.compiler
import scala.reflect.internal.settings.MutableSettings
import scala.reflect.internal.util.Position
import scala.tools.nsc.Settings
import scala.tools.nsc.reporters.{ ConsoleReporter, FilteringReporter }
/**
* Reporter used to compile *.sbt files that forwards compiler diagnostics to BSP clients
*/
abstract class EvalReporter extends FilteringReporter {
/**
* Send a final report to clear out the outdated diagnostics.
* @param sourceName a *.sbt file
*/
def finalReport(sourceName: String): Unit
}
object EvalReporter {
def console(s: Settings): EvalReporter = new ForwardingReporter(new ConsoleReporter(s))
}
class ForwardingReporter(delegate: FilteringReporter) extends EvalReporter {
def settings: Settings = delegate.settings
def doReport(pos: Position, msg: String, severity: Severity): Unit =
delegate.doReport(pos, msg, severity)
override def filter(pos: Position, msg: String, severity: Severity): Int =
delegate.filter(pos, msg, severity)
override def increment(severity: Severity): Unit = delegate.increment(severity)
override def errorCount: Int = delegate.errorCount
override def warningCount: Int = delegate.warningCount
override def hasErrors: Boolean = delegate.hasErrors
override def hasWarnings: Boolean = delegate.hasWarnings
override def comment(pos: Position, msg: String): Unit = delegate.comment(pos, msg)
override def cancelled: Boolean = delegate.cancelled
override def cancelled_=(b: Boolean): Unit = delegate.cancelled_=(b)
override def flush(): Unit = delegate.flush()
override def finish(): Unit = delegate.finish()
override def reset(): Unit =
delegate.reset() // super.reset not necessary, own state is never modified
override def rerunWithDetails(setting: MutableSettings#Setting, name: String): String =
delegate.rerunWithDetails(setting, name)
override def finalReport(sourceName: String): Unit = ()
}

View File

@ -19,7 +19,7 @@ import sbt.io.IO
class EvalTest extends Properties("eval") {
private[this] lazy val reporter = new StoreReporter(new Settings())
import reporter.ERROR
private[this] lazy val eval = new Eval(_ => reporter, None)
private[this] lazy val eval = new Eval(_ => new ForwardingReporter(reporter), None)
property("inferred integer") = forAll { (i: Int) =>
val result = eval.eval(i.toString)
@ -46,7 +46,7 @@ class EvalTest extends Properties("eval") {
property("backed local class") = forAll { (i: Int) =>
IO.withTemporaryDirectory { dir =>
val eval = new Eval(_ => reporter, backing = Some(dir))
val eval = new Eval(_ => new ForwardingReporter(reporter), backing = Some(dir))
val result = eval.eval(local(i))
val v = value(result).asInstanceOf[{ def i: Int }].i
(label("Value", v) |: (v == i)) &&

View File

@ -23,6 +23,7 @@ import sbt.librarymanagement.{ Resolver, UpdateReport }
import sbt.std.Transform.DummyTaskMap
import sbt.util.{ Logger, Show }
import sbt.BuildSyntax._
import sbt.internal.bsp.BuildTargetIdentifier
import scala.annotation.nowarn
import scala.Console.RED
@ -147,14 +148,15 @@ final case class PluginData(
unmanagedSourceDirectories: Seq[File],
unmanagedSources: Seq[File],
managedSourceDirectories: Seq[File],
managedSources: Seq[File]
managedSources: Seq[File],
buildTarget: Option[BuildTargetIdentifier]
) {
val classpath: Seq[Attributed[File]] = definitionClasspath ++ dependencyClasspath
}
object PluginData {
private[sbt] def apply(dependencyClasspath: Def.Classpath): PluginData =
PluginData(dependencyClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil)
PluginData(dependencyClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil, None)
}
object EvaluateTask {

View File

@ -8,19 +8,17 @@
package sbt
package internal
import java.io.File
import java.net.URI
import sbt.BuildPaths._
import sbt.Def.{ ScopeLocal, ScopedKey, Setting, isDummy }
import sbt.Keys._
import sbt.Project.inScope
import sbt.Scope.GlobalScope
import sbt.SlashSyntax0._
import sbt.compiler.Eval
import sbt.compiler.{ Eval, EvalReporter }
import sbt.internal.BuildStreams._
import sbt.internal.inc.classpath.ClasspathUtil
import sbt.internal.inc.{ ScalaInstance, ZincLmUtil, ZincUtil }
import sbt.internal.server.BuildServerEvalReporter
import sbt.internal.util.Attributed.data
import sbt.internal.util.Types.const
import sbt.internal.util.{ Attributed, Settings, ~> }
@ -31,6 +29,8 @@ import sbt.nio.Settings
import sbt.util.{ Logger, Show }
import xsbti.compile.{ ClasspathOptionsUtil, Compilers }
import java.io.File
import java.net.URI
import scala.annotation.{ nowarn, tailrec }
import scala.collection.mutable
import scala.tools.nsc.reporters.ConsoleReporter
@ -426,14 +426,21 @@ private[sbt] object Load {
() => eval
}
def mkEval(unit: BuildUnit): Eval =
mkEval(unit.definitions, unit.plugins, unit.plugins.pluginData.scalacOptions)
def mkEval(defs: LoadedDefinitions, plugs: LoadedPlugins, options: Seq[String]): Eval =
mkEval(defs.target ++ plugs.classpath, defs.base, options)
def mkEval(unit: BuildUnit): Eval = {
val defs = unit.definitions
mkEval(defs.target ++ unit.plugins.classpath, defs.base, unit.plugins.pluginData.scalacOptions)
}
def mkEval(classpath: Seq[File], base: File, options: Seq[String]): Eval =
new Eval(options, classpath, s => new ConsoleReporter(s), Some(evalOutputDirectory(base)))
mkEval(classpath, base, options, EvalReporter.console)
def mkEval(
classpath: Seq[File],
base: File,
options: Seq[String],
mkReporter: scala.tools.nsc.Settings => EvalReporter
): Eval =
new Eval(options, classpath, mkReporter, Some(evalOutputDirectory(base)))
/**
* This will clean up left-over files in the config-classes directory if they are no longer used.
@ -703,7 +710,13 @@ private[sbt] object Load {
// NOTE - because we create an eval here, we need a clean-eval later for this URI.
lazy val eval = timed("Load.loadUnit: mkEval", log) {
mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions)
def mkReporter(settings: scala.tools.nsc.Settings): EvalReporter =
plugs.pluginData.buildTarget match {
case None => EvalReporter.console(settings)
case Some(buildTarget) =>
new BuildServerEvalReporter(buildTarget, new ConsoleReporter(settings))
}
mkEval(plugs.classpath, defDir, plugs.pluginData.scalacOptions, mkReporter)
}
val initialProjects = defsScala.flatMap(b => projectsFromBuild(b, normBase)) ++ buildLevelExtraProjects
@ -1168,6 +1181,7 @@ private[sbt] object Load {
val unmanagedSrcs = (Configurations.Compile / unmanagedSources).value
val managedSrcDirs = (Configurations.Compile / managedSourceDirectories).value
val managedSrcs = (Configurations.Compile / managedSources).value
val buildTarget = (Configurations.Compile / bspTargetIdentifier).value
PluginData(
removeEntries(cp, prod),
prod,
@ -1178,6 +1192,7 @@ private[sbt] object Load {
unmanagedSrcs,
managedSrcDirs,
managedSrcs,
Some(buildTarget)
)
},
scalacOptions += "-Wconf:cat=unused-nowarn:s",
@ -1233,7 +1248,7 @@ private[sbt] object Load {
loadPluginDefinition(
dir,
config,
PluginData(config.globalPluginClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil)
PluginData(config.globalPluginClasspath, Nil, None, None, Nil, Nil, Nil, Nil, Nil, None)
)
def buildPlugins(dir: File, s: State, config: LoadBuildConfiguration): LoadedPlugins =
@ -1429,7 +1444,8 @@ final case class LoadBuildConfiguration(
Nil,
Nil,
Nil,
Nil
Nil,
None
)
case None => PluginData(globalPluginClasspath)
}

View File

@ -0,0 +1,93 @@
/*
* sbt
* Copyright 2011 - 2018, Lightbend, Inc.
* Copyright 2008 - 2010, Mark Harrah
* Licensed under Apache License 2.0 (see LICENSE)
*/
package sbt.internal.server
import sbt.StandardMain.exchange
import sbt.compiler.ForwardingReporter
import sbt.internal.bsp
import sbt.internal.bsp.{
BuildTargetIdentifier,
Diagnostic,
DiagnosticSeverity,
PublishDiagnosticsParams,
Range,
TextDocumentIdentifier
}
import java.nio.file.{ Files, Path, Paths }
import scala.collection.mutable
import scala.reflect.internal.Reporter
import scala.reflect.internal.util.{ DefinedPosition, Position }
import scala.tools.nsc.reporters.FilteringReporter
import sbt.internal.bsp.codec.JsonProtocol._
class BuildServerEvalReporter(buildTarget: BuildTargetIdentifier, delegate: FilteringReporter)
extends ForwardingReporter(delegate) {
private val problemsByFile = mutable.Map[Path, Vector[Diagnostic]]()
override def doReport(pos: Position, msg: String, severity: Severity): Unit = {
for {
filePath <- if (pos.source.file.exists) Some(Paths.get(pos.source.file.path)) else None
range <- convertToRange(pos)
} {
val bspSeverity = convertToBsp(severity)
val diagnostic = Diagnostic(range, bspSeverity, None, Option("sbt"), msg)
problemsByFile(filePath) = problemsByFile.getOrElse(filePath, Vector()) :+ diagnostic
val params = PublishDiagnosticsParams(
TextDocumentIdentifier(filePath.toUri),
buildTarget,
originId = None,
Vector(diagnostic),
reset = false
)
exchange.notifyEvent("build/publishDiagnostics", params)
}
super.doReport(pos, msg, severity)
}
override def finalReport(sourceName: String): Unit = {
val filePath = Paths.get(sourceName)
if (Files.exists(filePath)) {
val diagnostics = problemsByFile.getOrElse(filePath, Vector())
val params = PublishDiagnosticsParams(
textDocument = TextDocumentIdentifier(filePath.toUri),
buildTarget,
originId = None,
diagnostics,
reset = true
)
exchange.notifyEvent("build/publishDiagnostics", params)
}
}
private def convertToBsp(severity: Severity): Option[Long] = {
val result = severity match {
case Reporter.INFO => DiagnosticSeverity.Information
case Reporter.WARNING => DiagnosticSeverity.Warning
case Reporter.ERROR => DiagnosticSeverity.Error
}
Some(result)
}
private def convertToRange(pos: Position): Option[Range] = {
pos match {
case _: DefinedPosition =>
val startLine = pos.source.offsetToLine(pos.start)
val startChar = pos.start - pos.source.lineToOffset(startLine)
val endLine = pos.source.offsetToLine(pos.end)
val endChar = pos.end - pos.source.lineToOffset(endLine)
Some(
Range(
bsp.Position(startLine.toLong, startChar.toLong),
bsp.Position(endLine.toLong, endChar.toLong)
)
)
case _ => None
}
}
}

View File

@ -22,7 +22,7 @@ import sbt.StandardMain.exchange
import sbt.internal.bsp._
import sbt.internal.langserver.ErrorCodes
import sbt.internal.protocol.JsonRpcRequestMessage
import sbt.internal.util.Attributed
import sbt.internal.util.{ Attributed, ErrorHandling }
import sbt.internal.util.complete.{ Parser, Parsers }
import sbt.librarymanagement.CrossVersion.binaryScalaVersion
import sbt.librarymanagement.{ Configuration, ScalaArtifacts }
@ -53,35 +53,19 @@ object BuildServerProtocol {
)
private val bspReload = "bspReload"
private val bspReloadFailed = "bspReloadFailed"
private val bspReloadSucceed = "bspReloadSucceed"
lazy val commands: Seq[Command] = Seq(
Command.single(bspReload) { (state, reqId) =>
import sbt.BasicCommandStrings._
import sbt.internal.CommandStrings._
val result = List(
StashOnFailure,
s"$OnFailure $bspReloadFailed $reqId",
LoadProjectImpl,
s"$bspReloadSucceed $reqId",
PopOnFailure,
FailureWall
) ::: state
result
},
Command.single(bspReloadFailed) { (state, reqId) =>
exchange.respondError(
ErrorCodes.InternalError,
"reload failed",
Some(reqId),
state.source
)
state
},
Command.single(bspReloadSucceed) { (state, reqId) =>
exchange.respondEvent(JNull, Some(reqId), state.source)
state
try {
val newState = BuiltinCommands.doLoadProject(state, Project.LoadAction.Current)
exchange.respondEvent(JNull, Some(reqId), state.source)
newState
} catch {
case NonFatal(e) =>
val msg = ErrorHandling.reducedToString(e)
exchange.respondError(ErrorCodes.InternalError, msg, Some(reqId), state.source)
state.fail
}
}
)

View File

@ -114,7 +114,7 @@ object FakeState {
Nil
)
val pluginData = PluginData(Nil, Nil, None, None, Nil, Nil, Nil, Nil, Nil)
val pluginData = PluginData(Nil, Nil, None, None, Nil, Nil, Nil, Nil, Nil, None)
val builds: DetectedModules[BuildDef] = new DetectedModules[BuildDef](Nil)
val detectedAutoPlugins: Seq[DetectedAutoPlugin] =

View File

@ -8,10 +8,11 @@
package testpkg
import sbt.internal.bsp.SourcesResult
import sbt.internal.bsp.WorkspaceBuildTargetsResult
import sbt.internal.langserver.ErrorCodes
import sbt.IO
import java.io.File
import sbt.internal.bsp.WorkspaceBuildTargetsResult
import scala.concurrent.duration._
// starts svr using server-test/buildserver and perform custom server tests
@ -43,10 +44,10 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/sources") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
val buildTarget = buildTargetUri("util", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "24", "method": "buildTarget/sources", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/sources"))
@ -80,10 +81,10 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/compile") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
val buildTarget = buildTargetUri("util", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "32", "method": "buildTarget/compile", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/compile"))
@ -94,10 +95,10 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/scalacOptions") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
val buildTarget = buildTargetUri("util", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "40", "method": "buildTarget/scalacOptions", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/scalacOptions"))
@ -118,11 +119,66 @@ object BuildServerTest extends AbstractServerTest {
})
}
test("workspace/reload: send diagnostic and respond with error") { _ =>
// write an other-build.sbt file that does not compile
val otherBuildFile = new File(svr.baseDirectory, "other-build.sbt")
IO.write(
otherBuildFile,
"""
|val someSettings = Seq(
| scalacOptions ++= "-deprecation"
|)
|""".stripMargin
)
// reload
svr.sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": "52", "method": "workspace/reload"}"""
)
assert(
svr.waitForString(10.seconds) { s =>
s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") &&
s.contains(s""""textDocument":{"uri":"${otherBuildFile.toPath.toUri}"}""") &&
s.contains(""""severity":1""") &&
s.contains(""""reset":true""")
}
)
assert(
svr.waitForString(10.seconds) { s =>
s.contains(""""id":"52"""") &&
s.contains(""""error"""") &&
s.contains(s""""code":${ErrorCodes.InternalError}""") &&
s.contains("Type error in expression")
}
)
// fix the other-build.sbt file and reload again
IO.write(
otherBuildFile,
"""
|val someSettings = Seq(
| scalacOptions += "-deprecation"
|)
|""".stripMargin
)
svr.sendJsonRpc(
"""{ "jsonrpc": "2.0", "id": "52", "method": "workspace/reload"}"""
)
// assert received an empty diagnostic
assert(
svr.waitForString(10.seconds) { s =>
s.contains(s""""buildTarget":{"uri":"$metaBuildTarget"}""") &&
s.contains(s""""textDocument":{"uri":"${otherBuildFile.toPath.toUri}"}""") &&
s.contains(""""diagnostics":[]""") &&
s.contains(""""reset":true""")
}
)
IO.delete(otherBuildFile)
}
test("buildTarget/scalaMainClasses") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Compile"
val buildTarget = buildTargetUri("runAndTest", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "56", "method": "buildTarget/scalaMainClasses", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/scalaMainClasses"))
@ -133,10 +189,10 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/run") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Compile"
val buildTarget = buildTargetUri("runAndTest", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "64", "method": "buildTarget/run", "params": {
| "target": { "uri": "$x" },
| "target": { "uri": "$buildTarget" },
| "dataKind": "scala-main-class",
| "data": { "class": "main.Main" }
|} }""".stripMargin
@ -153,10 +209,10 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/scalaTestClasses") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
val buildTarget = buildTargetUri("runAndTest", "Test")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "72", "method": "buildTarget/scalaTestClasses", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/scalaTestClasses"))
@ -168,10 +224,10 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/test: run all tests") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
val buildTarget = buildTargetUri("runAndTest", "Test")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "80", "method": "buildTarget/test", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/test"))
@ -182,15 +238,15 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/test: run one test class") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#runAndTest/Test"
val buildTarget = buildTargetUri("runAndTest", "Test")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "84", "method": "buildTarget/test", "params": {
| "targets": [{ "uri": "$x" }],
| "targets": [{ "uri": "$buildTarget" }],
| "dataKind": "scala-test",
| "data": {
| "testClasses": [
| {
| "target": { "uri": "$x" },
| "target": { "uri": "$buildTarget" },
| "classes": ["tests.PassingTest"]
| }
| ]
@ -205,53 +261,53 @@ object BuildServerTest extends AbstractServerTest {
}
test("buildTarget/compile: report error") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#reportError/Compile"
val buildTarget = buildTargetUri("reportError", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "88", "method": "buildTarget/compile", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(svr.waitForString(10.seconds) { s =>
(s contains s""""buildTarget":{"uri":"$x"}""") &&
(s contains s""""buildTarget":{"uri":"$buildTarget"}""") &&
(s contains """"severity":1""") &&
(s contains """"reset":true""")
})
}
test("buildTarget/compile: report warning") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#reportWarning/Compile"
val buildTarget = buildTargetUri("reportWarning", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "90", "method": "buildTarget/compile", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(svr.waitForString(10.seconds) { s =>
(s contains s""""buildTarget":{"uri":"$x"}""") &&
(s contains s""""buildTarget":{"uri":"$buildTarget"}""") &&
(s contains """"severity":2""") &&
(s contains """"reset":true""")
})
}
test("buildTarget/compile: respond error") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#respondError/Compile"
val buildTarget = buildTargetUri("respondError", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "92", "method": "buildTarget/compile", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(svr.waitForString(10.seconds) { s =>
s.contains(""""id":"92"""") &&
s.contains(""""error"""") &&
s.contains(""""code":-32603""") &&
s.contains(s""""code":${ErrorCodes.InternalError}""") &&
s.contains("custom message")
})
}
test("buildTarget/resources") { _ =>
val x = s"${svr.baseDirectory.getAbsoluteFile.toURI}#util/Compile"
val buildTarget = buildTargetUri("util", "Compile")
svr.sendJsonRpc(
s"""{ "jsonrpc": "2.0", "id": "96", "method": "buildTarget/resources", "params": {
| "targets": [{ "uri": "$x" }]
| "targets": [{ "uri": "$buildTarget" }]
|} }""".stripMargin
)
assert(processing("buildTarget/resources"))
@ -281,4 +337,10 @@ object BuildServerTest extends AbstractServerTest {
msg.contains(s""""message":"Processing $method"""")
}
}
private def buildTargetUri(project: String, config: String): String =
s"${svr.baseDirectory.getAbsoluteFile.toURI}#$project/$config"
private def metaBuildTarget: String =
s"${svr.baseDirectory.getAbsoluteFile.toURI}project/#buildserver-build/Compile"
}