Skip to content

Commit 6caac63

Browse files
committed
IDE tests: compile dependent workspaces
When writing IDE tests that simulate multi-project setups, we need to compile the sources of the workspaces that are depended on, because the dependent workspaces require their class files. This commit changes the `TestServer` so that workspaces that are depended on are compiled during the initialization of the server. When there are no dependency relation between workspaces, no compilation is performed.
1 parent 361090e commit 6caac63

File tree

2 files changed

+75
-26
lines changed

2 files changed

+75
-26
lines changed

language-server/test/dotty/tools/languageserver/util/CodeTester.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ class CodeTester(workspaces: List[Workspace]) {
1717

1818
private val sources = for { workspace <- workspaces
1919
source <- workspace.sources } yield (workspace, source)
20-
private val files = sources.zipWithIndex.map {
21-
case ((workspace, ScalaSourceWithPositions(text, _)), i) => testServer.openCode(text, workspace, s"Source$i.scala")
22-
case ((workspace, WorksheetWithPositions(text, _)), i) => testServer.openCode(text, workspace, s"Worksheet$i.sc")
23-
}
20+
21+
private val files =
22+
for { workspace <- workspaces
23+
(source, id) <- workspace.sources.zipWithIndex } yield source match {
24+
case ScalaSourceWithPositions(text, _) => testServer.openCode(text, workspace, s"Source$id.scala")
25+
case WorksheetWithPositions(text, _) => testServer.openCode(text, workspace, s"Worksheet$id.sc")
26+
}
2427

2528
private val positions: PositionContext = getPositions(files)
2629

language-server/test/dotty/tools/languageserver/util/server/TestServer.scala

Lines changed: 68 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package dotty.tools.languageserver.util.server
22

33
import java.io.PrintWriter
4-
import java.io.File.{separator => sep}
4+
import java.io.File.{pathSeparator, separator}
55
import java.net.URI
66
import java.nio.file.{Files, Path}
77
import java.util
88

9+
import dotty.tools.dotc.Main
10+
import dotty.tools.dotc.reporting.{Reporter, ThrowingReporter}
11+
import dotty.tools.io.Directory
912
import dotty.tools.languageserver.DottyLanguageServer
1013
import dotty.tools.languageserver.util.Code.Workspace
1114
import org.eclipse.lsp4j.{ DidOpenTextDocumentParams, InitializeParams, InitializeResult, TextDocumentItem}
@@ -18,11 +21,21 @@ class TestServer(testFolder: Path, workspaces: List[Workspace]) {
1821
init()
1922

2023
private[this] def init(): InitializeResult = {
24+
var compiledWorkspaces: Set[Workspace] = Set.empty
25+
26+
/** Compile the dependencies of the given workspace, and then the workspace. */
27+
def compileWorkspaceAndDependencies(workspace: Workspace): Unit =
28+
if (!compiledWorkspaces.contains(workspace)) {
29+
workspace.dependsOn.foreach(compileWorkspaceAndDependencies)
30+
compileWorkspace(workspace)
31+
compiledWorkspaces += workspace
32+
}
33+
2134
/**
2235
* Set up given workspace, return JSON config.
2336
*
24-
* This creates the necessary directories to hold the classes and sources. Some values
25-
* are passed via sbt-buildinfo, such as the classpath containing the scala and dotty libaries.
37+
* If the workspace has dependencies, these dependencies are compiled. The classfiles of the
38+
* dependent workspaces are put on the classpath of this workspace.
2639
*
2740
* @param workspace The workspace to configure.
2841
* @return A JSON object representing the configuration for this workspace.
@@ -33,29 +46,16 @@ class TestServer(testFolder: Path, workspaces: List[Workspace]) {
3346
.map(elem => '"' + elem.toString.replace('\\', '/') + '"')
3447
.mkString("[ ", ", ", " ]")
3548

36-
def classDirectory(workspace: Workspace): Path = {
37-
val path = testFolder.resolve(workspace.name).resolve("out")
38-
Files.createDirectories(path)
39-
path.toAbsolutePath
40-
}
41-
42-
val dependencyClasspath =
43-
BuildInfo.ideTestsDependencyClasspath.map(_.getAbsolutePath) ++
44-
workspace.dependsOn.map(w => classDirectory(w).toString)
45-
46-
val sourceDirectory: Path = {
47-
val path = TestFile.sourceDir.resolve(workspace.name).toAbsolutePath
48-
Files.createDirectories(path)
49-
path
50-
}
49+
// Compile all the dependencies of this workspace
50+
workspace.dependsOn.foreach(compileWorkspaceAndDependencies)
5151

5252
s"""{
5353
| "id" : "${workspace.name}",
5454
| "compilerVersion" : "${BuildInfo.ideTestsCompilerVersion}",
5555
| "compilerArguments" : ${showSeq(BuildInfo.ideTestsCompilerArguments)},
56-
| "sourceDirectories" : ${showSeq(sourceDirectory :: Nil)},
57-
| "dependencyClasspath" : ${showSeq(dependencyClasspath)},
58-
| "classDirectory" : "${classDirectory(workspace).toString.replace('\\','/')}"
56+
| "sourceDirectories" : ${showSeq(sourceDirectory(workspace, wipe = false) :: Nil)},
57+
| "dependencyClasspath" : ${showSeq(dependencyClasspath(workspace))},
58+
| "classDirectory" : "${classDirectory(workspace, wipe = false).toString.replace('\\','/')}"
5959
|}
6060
|""".stripMargin
6161
}
@@ -83,7 +83,7 @@ class TestServer(testFolder: Path, workspaces: List[Workspace]) {
8383
* @return the file opened
8484
*/
8585
def openCode(code: String, workspace: Workspace, fileName: String): TestFile = {
86-
val testFile = new TestFile(workspace.name + sep + fileName)
86+
val testFile = new TestFile(workspace.name + separator + fileName)
8787
val dotdp = new DidOpenTextDocumentParams()
8888
val tdi = new TextDocumentItem()
8989
tdi.setUri(testFile.uri)
@@ -93,4 +93,50 @@ class TestServer(testFolder: Path, workspaces: List[Workspace]) {
9393
testFile
9494
}
9595

96+
private def classDirectory(workspace: Workspace, wipe: Boolean): Path = {
97+
val path = testFolder.resolve(workspace.name).resolve("out")
98+
if (wipe) {
99+
Directory(path).deleteRecursively()
100+
Files.createDirectories(path)
101+
}
102+
path.toAbsolutePath
103+
}
104+
105+
private def dependencyClasspath(workspace: Workspace) =
106+
BuildInfo.ideTestsDependencyClasspath.map(_.getAbsolutePath) ++
107+
workspace.dependsOn.map(w => classDirectory(w, wipe = false).toString)
108+
109+
private def sourceDirectory(workspace: Workspace, wipe: Boolean): Path = {
110+
val path = TestFile.sourceDir.resolve(workspace.name).toAbsolutePath
111+
if (wipe) {
112+
Directory(path).deleteRecursively()
113+
Files.createDirectories(path)
114+
}
115+
path
116+
}
117+
118+
/**
119+
* Sets up the sources of the given workspace, creates the necessary directories
120+
* and compile the sources.
121+
*
122+
* @param workspace The workspace to set up.
123+
*/
124+
private def compileWorkspace(workspace: Workspace): Unit = {
125+
val sourcesDir = sourceDirectory(workspace, wipe = true)
126+
val sources = workspace.sources.zipWithIndex.map { case (src, id) =>
127+
val path = sourcesDir.resolve(s"Source${id}.scala").toAbsolutePath
128+
Files.write(path, src.text.getBytes("UTF-8"))
129+
path.toString
130+
}
131+
132+
val compileOptions =
133+
sources.toArray ++
134+
Array(
135+
"-classpath", dependencyClasspath(workspace).mkString(pathSeparator),
136+
"-d", classDirectory(workspace, wipe = true).toString
137+
)
138+
val reporter = new ThrowingReporter(Reporter.NoReporter)
139+
Main.process(compileOptions, reporter)
140+
}
141+
96142
}

0 commit comments

Comments
 (0)