Skip to content

Commit d2d1917

Browse files
committed
Support multi-project setups in the IDE tests
1 parent 1d24eaa commit d2d1917

File tree

3 files changed

+106
-28
lines changed

3 files changed

+106
-28
lines changed

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

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,11 @@ object Code {
7474
}
7575
}
7676

77-
/** A new `CodeTester` working with `sources` in the workspace. */
78-
def withSources(sources: SourceWithPositions*): CodeTester = new CodeTester(sources.toList, Nil)
77+
/** A new `CodeTester` working with a single workspace containing `sources`. */
78+
def withSources(sources: SourceWithPositions*): CodeTester = withWorkspaces(Workspace(sources.toList))
79+
80+
/** A new `CodeTester` working with `workspaces`. */
81+
def withWorkspaces(workspaces: Workspace*): CodeTester = new CodeTester(workspaces.toList)
7982

8083
/**
8184
* A virtual source file where several markers have been set.
@@ -85,7 +88,46 @@ object Code {
8588
*/
8689
case class SourceWithPositions(text: String, positions: List[(CodeMarker, Int, Int)]) {
8790
/** A new `CodeTester` with only this source in the workspace. */
88-
def withSource: CodeTester = new CodeTester(this :: Nil, Nil)
91+
def withSource: CodeTester = withSources(this)
92+
}
93+
94+
/**
95+
* A group of sources belonging to the same project.
96+
*
97+
* @param sources The sources that this workspace holds.
98+
* @param name The name of this workspace
99+
* @param dependsOn The other workspaces on which this workspace depend.
100+
*/
101+
case class Workspace(sources: List[SourceWithPositions],
102+
name: String = Workspace.freshName,
103+
dependsOn: List[Workspace] = Nil) {
104+
105+
/**
106+
* Add `sources` to the sources of this workspace.
107+
*/
108+
def withSources(sources: SourceWithPositions*): Workspace = copy(sources = this.sources ::: sources.toList)
109+
110+
}
111+
112+
object Workspace {
113+
private[this] val count = new java.util.concurrent.atomic.AtomicInteger()
114+
private def freshName: String = s"workspace${count.incrementAndGet()}"
115+
116+
/**
117+
* Creates a new workspace that depends on `workspaces`.
118+
*
119+
* @param workspaces The dependencies of the new workspace.
120+
* @return An empty workspace with a dependency on the specified workspaces.
121+
*/
122+
def dependingOn(workspaces: Workspace*) = new Workspace(Nil, dependsOn = workspaces.toList)
123+
124+
/**
125+
* Create a new workspace with the given sources.
126+
*
127+
* @param sources The sources to add to this workspace.
128+
* @return a new workspace containing the specified sources.
129+
*/
130+
def withSources(sources: SourceWithPositions*): Workspace = new Workspace(sources.toList)
89131
}
90132

91133
}

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

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

3-
import dotty.tools.languageserver.util.Code.SourceWithPositions
3+
import dotty.tools.languageserver.util.Code.{SourceWithPositions, Workspace}
44
import dotty.tools.languageserver.util.actions._
55
import dotty.tools.languageserver.util.embedded.CodeMarker
66
import dotty.tools.languageserver.util.server.{TestFile, TestServer}
@@ -10,15 +10,18 @@ import org.eclipse.lsp4j.{CompletionItemKind, DocumentHighlightKind}
1010
* Simulates an LSP client for test in a workspace defined by `sources`.
1111
*
1212
* @param sources The list of sources in the workspace
13-
* @param actions Unused
1413
*/
15-
class CodeTester(sources: List[SourceWithPositions], actions: List[Action]) {
14+
class CodeTester(workspaces: List[Workspace]) {
1615

17-
private val testServer = new TestServer(TestFile.testDir)
16+
private val testServer = new TestServer(TestFile.testDir, workspaces)
1817

19-
private val files = sources.zipWithIndex.map { case (code, i) =>
20-
testServer.openCode(code.text, s"Source$i.scala")
18+
private val sources = for { workspace <- workspaces
19+
source <- workspace.sources } yield (workspace, source)
20+
21+
private val files = sources.zipWithIndex.map { case ((workspace, source), i) =>
22+
testServer.openCode(source.text, workspace, s"Source${i}.scala")
2123
}
24+
2225
private val positions: PositionContext = getPositions(files)
2326

2427
/**
@@ -121,7 +124,13 @@ class CodeTester(sources: List[SourceWithPositions], actions: List[Action]) {
121124
action.execute()(testServer, positions)
122125
} catch {
123126
case ex: AssertionError =>
124-
val sourcesStr = sources.zip(files).map{ case (source, file) => "// " + file.file + "\n" + source.text}.mkString("\n")
127+
val sourcesStr =
128+
sources.zip(files).map {
129+
case ((workspace, source), file) =>
130+
s"""// ${file.file} in workspace ${workspace.name}
131+
|${source.text}""".stripMargin
132+
}.mkString(System.lineSeparator)
133+
125134
val msg =
126135
s"""
127136
|
@@ -140,7 +149,7 @@ class CodeTester(sources: List[SourceWithPositions], actions: List[Action]) {
140149
private def getPositions(files: List[TestFile]): PositionContext = {
141150
val posSeq = {
142151
for {
143-
(code, file) <- sources.zip(files)
152+
((_, code), file) <- sources.zip(files)
144153
(position, line, char) <- code.positions
145154
} yield position -> (file, line, char)
146155
}

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

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

33
import java.io.PrintWriter
4+
import java.io.File.{separator => sep}
45
import java.net.URI
5-
import java.nio.file.Path
6+
import java.nio.file.{Files, Path}
67
import java.util
78

89
import dotty.tools.languageserver.DottyLanguageServer
10+
import dotty.tools.languageserver.util.Code.Workspace
911
import org.eclipse.lsp4j.{ DidOpenTextDocumentParams, InitializeParams, InitializeResult, TextDocumentItem}
1012

11-
class TestServer(testFolder: Path) {
13+
class TestServer(testFolder: Path, workspaces: List[Workspace]) {
1214

1315
val server = new DottyLanguageServer
1416
init()
1517

1618
private[this] def init(): InitializeResult = {
17-
// Fill the configuration with values populated by sbt
18-
def showSeq[T](lst: Seq[T]): String = lst.map(elem => '"' + elem.toString + '"').mkString("[ ", ", ", " ]")
19-
val dottyIdeJson: String =
20-
s"""[ {
21-
| "id" : "dotty-ide-test",
19+
/**
20+
* Set up given workspace, return JSON config.
21+
*
22+
* This creates the necessary directories to hold the classes and sources. Some values
23+
* are passed via sbt-buildinfo, such as the classpath containing the scala and dotty libaries.
24+
*
25+
* @param workspace The workspace to configure.
26+
* @return A JSON object representing the configuration for this workspace.
27+
*/
28+
def workspaceSetup(workspace: Workspace): String = {
29+
def showSeq[T](lst: Seq[T]): String = lst.map(elem => '"' + elem.toString + '"').mkString("[ ", ", ", " ]")
30+
31+
def classDirectory(workspace: Workspace): Path = {
32+
val path = testFolder.resolve(workspace.name).resolve("out")
33+
Files.createDirectories(path)
34+
path.toAbsolutePath
35+
}
36+
37+
val dependencyClasspath =
38+
BuildInfo.ideTestsDependencyClasspath.map(_.getAbsolutePath) ++
39+
workspace.dependsOn.map(w => classDirectory(w).toString)
40+
41+
val sourceDirectory: Path = {
42+
val path = TestFile.sourceDir.resolve(workspace.name).toAbsolutePath
43+
Files.createDirectories(path)
44+
path
45+
}
46+
47+
s"""{
48+
| "id" : "${workspace.name}",
2249
| "compilerVersion" : "${BuildInfo.ideTestsCompilerVersion}",
2350
| "compilerArguments" : ${showSeq(BuildInfo.ideTestsCompilerArguments)},
24-
| "sourceDirectories" : ${showSeq(BuildInfo.ideTestsSourceDirectories)},
25-
| "dependencyClasspath" : ${showSeq(BuildInfo.ideTestsDependencyClasspath)},
26-
| "classDirectory" : "${BuildInfo.ideTestsClassDirectory}"
51+
| "sourceDirectories" : ${showSeq(sourceDirectory :: Nil)},
52+
| "dependencyClasspath" : ${showSeq(dependencyClasspath)},
53+
| "classDirectory" : "${classDirectory(workspace)}"
2754
|}
28-
|]""".stripMargin
55+
|""".stripMargin
56+
}
57+
2958
val configFile = testFolder.resolve(DottyLanguageServer.IDE_CONFIG_FILE)
30-
testFolder.toFile.mkdirs()
31-
testFolder.resolve("src").toFile.mkdirs()
32-
testFolder.resolve("out").toFile.mkdirs()
59+
val configuration = workspaces.map(workspaceSetup).mkString("[", ",", "]")
3360

3461
new PrintWriter(configFile.toString) {
35-
write(dottyIdeJson)
62+
write(configuration)
3663
close()
3764
}
3865

@@ -49,8 +76,8 @@ class TestServer(testFolder: Path) {
4976
* @param fileName file path in the source directory
5077
* @return the file opened
5178
*/
52-
def openCode(code: String, fileName: String): TestFile = {
53-
val testFile = new TestFile(fileName)
79+
def openCode(code: String, workspace: Workspace, fileName: String): TestFile = {
80+
val testFile = new TestFile(workspace.name + sep + fileName)
5481
val dotdp = new DidOpenTextDocumentParams()
5582
val tdi = new TextDocumentItem()
5683
tdi.setUri(testFile.uri)

0 commit comments

Comments
 (0)