Skip to content

Commit 9ef88ae

Browse files
committed
Support multi-project setups in the IDE tests
1 parent 7087a6c commit 9ef88ae

File tree

3 files changed

+108
-30
lines changed

3 files changed

+108
-30
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
@@ -99,8 +99,11 @@ object Code {
9999
}
100100
}
101101

102-
/** A new `CodeTester` working with `sources` in the workspace. */
103-
def withSources(sources: SourceWithPositions*): CodeTester = new CodeTester(sources.toList, Nil)
102+
/** A new `CodeTester` working with a single workspace containing `sources`. */
103+
def withSources(sources: SourceWithPositions*): CodeTester = withWorkspaces(Workspace(sources.toList))
104+
105+
/** A new `CodeTester` working with `workspaces`. */
106+
def withWorkspaces(workspaces: Workspace*): CodeTester = new CodeTester(workspaces.toList)
104107

105108
sealed trait SourceWithPositions {
106109

@@ -111,7 +114,7 @@ object Code {
111114
def positions: List[(CodeMarker, Int, Int)]
112115

113116
/** A new `CodeTester` with only this source in the workspace. */
114-
def withSource: CodeTester = new CodeTester(this :: Nil, Nil)
117+
def withSource: CodeTester = withSources(this)
115118

116119
}
117120

@@ -131,4 +134,43 @@ object Code {
131134
*/
132135
case class WorksheetWithPositions(text: String, positions: List[(CodeMarker, Int, Int)]) extends SourceWithPositions
133136

137+
/**
138+
* A group of sources belonging to the same project.
139+
*
140+
* @param sources The sources that this workspace holds.
141+
* @param name The name of this workspace
142+
* @param dependsOn The other workspaces on which this workspace depend.
143+
*/
144+
case class Workspace(sources: List[SourceWithPositions],
145+
name: String = Workspace.freshName,
146+
dependsOn: List[Workspace] = Nil) {
147+
148+
/**
149+
* Add `sources` to the sources of this workspace.
150+
*/
151+
def withSources(sources: SourceWithPositions*): Workspace = copy(sources = this.sources ::: sources.toList)
152+
153+
}
154+
155+
object Workspace {
156+
private[this] val count = new java.util.concurrent.atomic.AtomicInteger()
157+
private def freshName: String = s"workspace${count.incrementAndGet()}"
158+
159+
/**
160+
* Creates a new workspace that depends on `workspaces`.
161+
*
162+
* @param workspaces The dependencies of the new workspace.
163+
* @return An empty workspace with a dependency on the specified workspaces.
164+
*/
165+
def dependingOn(workspaces: Workspace*) = new Workspace(Nil, dependsOn = workspaces.toList)
166+
167+
/**
168+
* Create a new workspace with the given sources.
169+
*
170+
* @param sources The sources to add to this workspace.
171+
* @return a new workspace containing the specified sources.
172+
*/
173+
def withSources(sources: SourceWithPositions*): Workspace = new Workspace(sources.toList)
174+
}
175+
134176
}

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +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

18+
private val sources = for { workspace <- workspaces
19+
source <- workspace.sources } yield (workspace, source)
1920
private val files = sources.zipWithIndex.map {
20-
case (ScalaSourceWithPositions(text, _), i) => testServer.openCode(text, s"Source$i.scala")
21-
case (WorksheetWithPositions(text, _), i) => testServer.openCode(text, s"Worksheet$i.sc")
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")
2223
}
24+
2325
private val positions: PositionContext = getPositions(files)
2426

2527
/**
@@ -158,7 +160,13 @@ class CodeTester(sources: List[SourceWithPositions], actions: List[Action]) {
158160
action.execute()(testServer, testServer.client, positions)
159161
} catch {
160162
case ex: AssertionError =>
161-
val sourcesStr = sources.zip(files).map{ case (source, file) => "// " + file.file + "\n" + source.text}.mkString("\n")
163+
val sourcesStr =
164+
sources.zip(files).map {
165+
case ((workspace, source), file) =>
166+
s"""// ${file.file} in workspace ${workspace.name}
167+
|${source.text}""".stripMargin
168+
}.mkString(System.lineSeparator)
169+
162170
val msg =
163171
s"""
164172
|
@@ -177,7 +185,7 @@ class CodeTester(sources: List[SourceWithPositions], actions: List[Action]) {
177185
private def getPositions(files: List[TestFile]): PositionContext = {
178186
val posSeq = {
179187
for {
180-
(code, file) <- sources.zip(files)
188+
((_, code), file) <- sources.zip(files)
181189
(position, line, char) <- code.positions
182190
} yield position -> (file, line, char)
183191
}

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

Lines changed: 48 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,71 @@
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
var client: TestClient = _
1517

1618
init()
1719

1820
private[this] def init(): InitializeResult = {
19-
// Fill the configuration with values populated by sbt
20-
def showSeq[T](lst: Seq[T]): String =
21-
lst
22-
.map(elem => '"' + elem.toString.replace('\\', '/') + '"')
23-
.mkString("[ ", ", ", " ]")
24-
val dottyIdeJson: String =
25-
s"""[ {
26-
| "id" : "dotty-ide-test",
21+
/**
22+
* Set up given workspace, return JSON config.
23+
*
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.
26+
*
27+
* @param workspace The workspace to configure.
28+
* @return A JSON object representing the configuration for this workspace.
29+
*/
30+
def workspaceSetup(workspace: Workspace): String = {
31+
def showSeq[T](lst: Seq[T]): String =
32+
lst
33+
.map(elem => '"' + elem.toString.replace('\\', '/') + '"')
34+
.mkString("[ ", ", ", " ]")
35+
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+
}
51+
52+
s"""{
53+
| "id" : "${workspace.name}",
2754
| "compilerVersion" : "${BuildInfo.ideTestsCompilerVersion}",
2855
| "compilerArguments" : ${showSeq(BuildInfo.ideTestsCompilerArguments)},
29-
| "sourceDirectories" : ${showSeq(BuildInfo.ideTestsSourceDirectories)},
30-
| "dependencyClasspath" : ${showSeq(BuildInfo.ideTestsDependencyClasspath)},
31-
| "classDirectory" : "${BuildInfo.ideTestsClassDirectory.toString.replace('\\','/')}"
56+
| "sourceDirectories" : ${showSeq(sourceDirectory :: Nil)},
57+
| "dependencyClasspath" : ${showSeq(dependencyClasspath)},
58+
| "classDirectory" : "${classDirectory(workspace).toString.replace('\\','/')}"
3259
|}
33-
|]""".stripMargin
60+
|""".stripMargin
61+
}
62+
63+
Files.createDirectories(testFolder)
3464
val configFile = testFolder.resolve(DottyLanguageServer.IDE_CONFIG_FILE)
35-
testFolder.toFile.mkdirs()
36-
testFolder.resolve("src").toFile.mkdirs()
37-
testFolder.resolve("out").toFile.mkdirs()
65+
val configuration = workspaces.map(workspaceSetup).mkString("[", ",", "]")
3866

3967
new PrintWriter(configFile.toString) {
40-
write(dottyIdeJson)
68+
write(configuration)
4169
close()
4270
}
4371

@@ -54,8 +82,8 @@ class TestServer(testFolder: Path) {
5482
* @param fileName file path in the source directory
5583
* @return the file opened
5684
*/
57-
def openCode(code: String, fileName: String): TestFile = {
58-
val testFile = new TestFile(fileName)
85+
def openCode(code: String, workspace: Workspace, fileName: String): TestFile = {
86+
val testFile = new TestFile(workspace.name + sep + fileName)
5987
val dotdp = new DidOpenTextDocumentParams()
6088
val tdi = new TextDocumentItem()
6189
tdi.setUri(testFile.uri)

0 commit comments

Comments
 (0)