Skip to content

Commit

Permalink
WIP Add BSP IT
Browse files Browse the repository at this point in the history
  • Loading branch information
alexarchambault committed Sep 26, 2024
1 parent e053489 commit 1d70b17
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 18 deletions.
15 changes: 0 additions & 15 deletions integration/ide/bsp-install/resources/build.mill

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"displayName": "mill-bsp",
"version": "<MILL_VERSION>",
"bspVersion": "<BSP_VERSION>",
"capabilities": {
"compileProvider": {
"languageIds": [
"java",
"scala"
]
},
"testProvider": {
"languageIds": [
"java",
"scala"
]
},
"runProvider": {
"languageIds": [
"java",
"scala"
]
},
"debugProvider": {
"languageIds": []
},
"inverseSourcesProvider": true,
"dependencySourcesProvider": true,
"dependencyModulesProvider": true,
"resourcesProvider": true,
"outputPathsProvider": true,
"buildTargetChangedProvider": false,
"jvmRunEnvironmentProvider": true,
"jvmTestEnvironmentProvider": true,
"canReload": true,
"jvmCompileClasspathProvider": false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{
"targets": [
{
"id": {
"uri": "file:///workspace/hello-bsp"
},
"displayName": "hello-bsp",
"baseDirectory": "file:///workspace/hello-bsp",
"tags": [
"library",
"application"
],
"languageIds": [
"java",
"scala"
],
"dependencies": [],
"capabilities": {
"canCompile": true,
"canTest": false,
"canRun": true,
"canDebug": false
},
"dataKind": "scala",
"data": {
"scalaOrganization": "org.scala-lang",
"scalaVersion": "2.13.14",
"scalaBinaryVersion": "2.13",
"platform": 1,
"jars": [
"file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.13.14/scala-compiler-2.13.14.jar",
"file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect/2.13.14/scala-reflect-2.13.14.jar",
"file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.14/scala-library-2.13.14.jar",
"file:///coursier-cache/https/repo1.maven.org/maven2/io/github/java-diff-utils/java-diff-utils/4.12/java-diff-utils-4.12.jar",
"file:///coursier-cache/https/repo1.maven.org/maven2/org/jline/jline/3.25.1/jline-3.25.1.jar",
"file:///coursier-cache/https/repo1.maven.org/maven2/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar"
]
}
},
{
"id": {
"uri": "file:///workspace/mill-build"
},
"displayName": "mill-build/",
"baseDirectory": "file:///workspace/mill-build",
"tags": [
"library",
"application"
],
"languageIds": [
"java",
"scala"
],
"dependencies": [],
"capabilities": {
"canCompile": true,
"canTest": false,
"canRun": true,
"canDebug": false
},
"dataKind": "scala",
"data": {
"scalaOrganization": "org.scala-lang",
"scalaVersion": "2.13.14",
"scalaBinaryVersion": "2.13",
"platform": 1,
"jars": [
"file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-compiler/2.13.14/scala-compiler-2.13.14.jar",
"file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-reflect/2.13.14/scala-reflect-2.13.14.jar",
"file:///coursier-cache/https/repo1.maven.org/maven2/org/scala-lang/scala-library/2.13.14/scala-library-2.13.14.jar"
]
}
},
{
"id": {
"uri": "file:///workspace/mill-synthetic-root-target"
},
"displayName": "run-1-root",
"baseDirectory": "file:///workspace",
"tags": [
"manual"
],
"languageIds": [],
"dependencies": [],
"capabilities": {
"canCompile": false,
"canTest": false,
"canRun": false,
"canDebug": false
}
}
]
}
8 changes: 8 additions & 0 deletions integration/ide/bsp-install/resources/project/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package build

import mill._
import mill.scalalib._

object `hello-bsp` extends ScalaModule {
def scalaVersion = Option(System.getenv("MILL_TEST_SCALA_2_13_VERSION")).getOrElse(???)
}
Empty file.
144 changes: 142 additions & 2 deletions integration/ide/bsp-install/src/BspInstallTests.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,163 @@
package mill.integration

import mill.testkit.UtestIntegrationTestSuite
import ch.epfl.scala.{bsp4j => b}
import com.google.gson.{Gson, GsonBuilder}
import coursier.cache.CacheDefaults
import mill.api.BuildInfo
import mill.bsp.Constants
import mill.testkit.UtestIntegrationTestSuite
import org.eclipse.{lsp4j => l}
import utest._

import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.{Executors, ThreadFactory}

import scala.jdk.CollectionConverters._
import scala.reflect.ClassTag

object BspInstallTests extends UtestIntegrationTestSuite {
val bsp4jVersion: String = sys.props.getOrElse("BSP4J_VERSION", ???)

def fixturePath: os.Path =
super.workspaceSourcePath / "fixtures"
override protected def workspaceSourcePath: os.Path =
super.workspaceSourcePath / "project"

val updateFixtures = false
val gson = new Gson
val gsonBuilder = new GsonBuilder().setPrettyPrinting().create()
def compareWithGsonFixture[T: ClassTag](
value: T,
fixturePath: os.SubPath,
replaceAll: Seq[(String, String)] = Nil
): Unit = {

def doReplaceAll(input: String, inverse: Boolean = false): String =
replaceAll.foldLeft(input) {
case (input0, (from0, to0)) =>
val (from, to) = if (inverse) (to0, from0) else (from0, to0)
input0.replaceAll(from, to)
}

val fixturePath0 = BspInstallTests.fixturePath / fixturePath
val exists = os.exists(fixturePath0)
val readOpt = Option.when(exists) {
val bytes = os.read.bytes(fixturePath0)
gson.fromJson(
doReplaceAll(new String(bytes), inverse = true),
implicitly[ClassTag[T]].runtimeClass
)
}

if (!readOpt.contains(value))
if (updateFixtures) {
System.err.println(if (exists) s"Updating $fixturePath0" else s"Writing $fixturePath0")
val jsonStr = gsonBuilder.toJson(
value,
implicitly[ClassTag[T]].runtimeClass
)
os.write.over(fixturePath0, doReplaceAll(jsonStr))
} else
Predef.assert(
false,
if (exists) s"Error: value differs from fixture at $fixturePath0"
else s"Error: no fixture found at $fixturePath0"
)
}

def tests: Tests = Tests {
test("BSP install") - integrationTest { tester =>
import tester._
assert(eval("mill.bsp.BSP/install").isSuccess)
eval("mill.bsp.BSP/install", stdout = os.Inherit, stderr = os.Inherit, check = true)
val jsonFile = workspacePath / Constants.bspDir / s"${Constants.serverName}.json"
assert(os.exists(jsonFile))
val contents = os.read(jsonFile)
assert(
!contents.contains("--debug"),
contents.contains(s""""bspVersion":"${bsp4jVersion}"""")
)
pprint.err.log(contents)

val contentsJson = ujson.read(contents)
val bspCommand = contentsJson("argv").arr.map(_.str)
val proc = os.proc(bspCommand).spawn(
cwd = workspacePath,
stderr = os.Inherit,
env = millTestSuiteEnv
)

val client: b.BuildClient =
new b.BuildClient {
def onBuildLogMessage(params: b.LogMessageParams): Unit = ()
def onBuildPublishDiagnostics(params: b.PublishDiagnosticsParams): Unit = ()
def onBuildShowMessage(params: b.ShowMessageParams): Unit = ()
def onBuildTargetDidChange(params: b.DidChangeBuildTarget): Unit = ()
def onBuildTaskFinish(params: b.TaskFinishParams): Unit = ()
def onBuildTaskProgress(params: b.TaskProgressParams): Unit = ()
def onBuildTaskStart(params: b.TaskStartParams): Unit = ()
def onRunPrintStderr(params: b.PrintParams): Unit = ()
def onRunPrintStdout(params: b.PrintParams): Unit = ()
}
val pool = Executors.newCachedThreadPool(
new ThreadFactory {
val counter = new AtomicInteger
def newThread(runnable: Runnable): Thread = {
val t = new Thread(runnable, s"mill-bsp-integration-${counter.incrementAndGet()}")
t.setDaemon(true)
t
}
}
)
val launcher = new l.jsonrpc.Launcher.Builder[b.BuildServer]
.setExecutorService(pool)
.setInput(proc.stdout.wrapped)
.setOutput(proc.stdin.wrapped)
.setRemoteInterface(classOf[b.BuildServer])
.setLocalService(client)
.setExceptionHandler { t =>
System.err.println(s"Error during LSP processing: $t")
t.printStackTrace(System.err)
l.jsonrpc.RemoteEndpoint.DEFAULT_EXCEPTION_HANDLER.apply(t)
}
.create()

launcher.startListening()

val buildServer = launcher.getRemoteProxy()

val initRes = buildServer.buildInitialize(
new b.InitializeBuildParams(
"Mill Integration",
BuildInfo.millVersion,
b.Bsp4j.PROTOCOL_VERSION,
workspacePath.toNIO.toUri.toASCIIString,
new b.BuildClientCapabilities(List("scala", "java", "kotlin").asJava)
)
).get()
compareWithGsonFixture(
initRes,
os.sub / "initialize-build-result.json",
replaceAll = Seq(
BuildInfo.millVersion -> "<MILL_VERSION>",
Constants.bspProtocolVersion -> "<BSP_VERSION>"
)
)

val buildTargets = buildServer.workspaceBuildTargets().get()
pprint.err.log(buildTargets)
compareWithGsonFixture(
buildTargets,
os.sub / "workspace-build-targets.json",
replaceAll = Seq(
workspacePath.toNIO.toUri.toASCIIString.stripSuffix("/") -> "file:///workspace",
CacheDefaults.location.toPath.toUri.toASCIIString -> "file:///coursier-cache/"
)
)

proc.stdin.close()
proc.stdout.close()

proc.join(2000L)
}
}
}
7 changes: 6 additions & 1 deletion integration/package.mill
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,15 @@ object `package` extends RootModule {
object feature extends Cross[IntegrationCrossModule](build.listIn(millSourcePath / "feature"))
object invalidation
extends Cross[IntegrationCrossModule](build.listIn(millSourcePath / "invalidation"))
object ide extends Cross[IntegrationCrossModule](build.listIn(millSourcePath / "ide"))
object ide extends Cross[Ide](build.listIn(millSourcePath / "ide"))
trait IntegrationCrossModule extends build.MillScalaModule with IntegrationTestModule {
override def moduleDeps = super[IntegrationTestModule].moduleDeps
}
trait Ide extends IntegrationCrossModule {
def ivyDeps = super.ivyDeps() ++ Agg(
build.Deps.bsp4j
)
}

/** Deploy freshly build mill for use in tests */
def testMill: T[PathRef] = {
Expand Down

0 comments on commit 1d70b17

Please sign in to comment.