Skip to content

Commit f9185a4

Browse files
committed
Add IDE tests
1 parent be50d2c commit f9185a4

21 files changed

+553
-8
lines changed

.drone.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,18 @@ pipeline:
4343
- cp -R . /tmp/3/ && cd /tmp/3/
4444
- ./project/scripts/sbt dotty-optimised/test
4545

46-
test_sbt:
46+
test_ide:
4747
group: test
4848
image: lampepfl/dotty:2017-11-17
4949
commands:
5050
- cp -R . /tmp/4/ && cd /tmp/4/
51+
- ./project/scripts/sbt dotty-language-server/test:run
52+
53+
test_sbt:
54+
group: test
55+
image: lampepfl/dotty:2017-11-17
56+
commands:
57+
- cp -R . /tmp/5/ && cd /tmp/5/
5158
- ./project/scripts/sbt sbt-dotty/scripted
5259
when:
5360
# sbt scripted tests are slow and don't run on PRs

compiler/src/dotty/tools/dotc/util/DiffUtil.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ object DiffUtil {
5959
(fnd, exp, totalChange.toDouble / (expected.length + found.length))
6060
}
6161

62+
def mkColoredLineDiff(expected: Seq[String], actual: Seq[String]): String = {
63+
val expectedSize = DiffUtil.EOF.length max expected.map(_.length).max
64+
actual.padTo(expected.length, "").zip(expected.padTo(actual.length, "")).map { case (act, exp) =>
65+
mkColoredLineDiff(exp, act, expectedSize)
66+
}.mkString("\n")
67+
}
68+
6269
def mkColoredLineDiff(expected: String, actual: String, expectedSize: Int): String = {
6370
lazy val diff = {
6471
val tokens = splitTokens(expected, Nil).toArray
@@ -104,8 +111,8 @@ object DiffUtil {
104111
}.mkString
105112
}
106113

107-
private def added(str: String): String = bgColored(str, Console.GREEN)
108-
private def deleted(str: String) = bgColored(str, Console.RED)
114+
private def added(str: String): String = bgColored(str, Console.GREEN_B)
115+
private def deleted(str: String) = bgColored(str, Console.RED_B)
109116
private def bgColored(str: String, color: String): String = {
110117
if (str.isEmpty) ""
111118
else {

compiler/test/dotty/tools/vulpix/ParallelTesting.scala

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -535,10 +535,7 @@ trait ParallelTesting extends RunnerOrchestration { self =>
535535

536536
if (outputLines.length != checkLines.length || !linesMatch) {
537537
// Print diff to files and summary:
538-
val expectedSize = DiffUtil.EOF.length max checkLines.map(_.length).max
539-
val diff = outputLines.padTo(checkLines.length, "").zip(checkLines.padTo(outputLines.length, "")).map { case (act, exp) =>
540-
DiffUtil.mkColoredLineDiff(exp, act, expectedSize)
541-
}.mkString("\n")
538+
val diff = DiffUtil.mkColoredLineDiff(checkLines, outputLines)
542539

543540
val msg =
544541
s"""|Output from '$sourceTitle' did not match check file.

language-server/src/dotty/tools/languageserver/DottyLanguageServer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ class DottyLanguageServer extends LanguageServer
6060
val configFile = new File(new URI(rootUri + '/' + IDE_CONFIG_FILE))
6161
val configs: List[ProjectConfig] = (new ObjectMapper).readValue(configFile, classOf[Array[ProjectConfig]]).toList
6262

63-
val defaultFlags = List(/*"-Yplain-printer","-Yprint-pos"*/)
63+
val defaultFlags = List("-color:never" /*, "-Yplain-printer","-Yprint-pos"*/)
6464

6565
myDrivers = new mutable.HashMap
6666
for (config <- configs) {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package dotty.tools.languageserver
2+
3+
import org.junit.Test
4+
5+
import dotty.tools.languageserver.util._
6+
import dotty.tools.languageserver.util.Code._
7+
8+
class DefinitionTest extends BaseTest {
9+
10+
@Test def classDefinition0: Unit = {
11+
val refToFoo = "Foo".definition("Foo.scala".ref(m1, m2))
12+
checkActions(
13+
"Foo.scala" -> code"class ${m1}Foo$m2 { new $refToFoo }",
14+
"Bar.scala" -> code"class Bar { val foo: $refToFoo = new $refToFoo }"
15+
)
16+
}
17+
18+
@Test def valDefinition0: Unit = {
19+
val refToX = "x".definition("Foo.scala".ref(m1, m2))
20+
checkActions(
21+
"Foo.scala" -> code"class Foo { val ${m1}x$m2 = 0; $refToX }",
22+
"Bar.scala" -> code"class Bar { val foo = new Foo; foo.$refToX }"
23+
)
24+
}
25+
26+
@Test def defDefinition0: Unit = {
27+
val refToX = "x".definition("Foo.scala".ref(m1, m2))
28+
checkActions(
29+
"Foo.scala" -> code"class Foo { def ${m1}x$m2 = 0; $refToX }",
30+
"Bar.scala" -> code"class Bar { val foo = new Foo; foo.$refToX }"
31+
)
32+
}
33+
34+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dotty.tools.languageserver
2+
3+
import org.junit.Test
4+
5+
import dotty.tools.languageserver.util.BaseTest
6+
import dotty.tools.languageserver.util.Code._
7+
8+
class HoverTest extends BaseTest {
9+
10+
@Test def hoverOnWhiteSpace0: Unit = checkActions(code"${" " hover ""}")
11+
@Test def hoverOnWhiteSpace1: Unit = checkActions(code"${" " hover ""}class Foo")
12+
@Test def hoverOnWhiteSpace2: Unit = checkActions(code"class Foo ${" " hover ""}")
13+
@Test def hoverOnWhiteSpace3: Unit = checkActions(code"class Foo { } ${" " hover ""}")
14+
15+
@Test def hoverOnClass0: Unit = checkActions(code"class ${"Foo" hover "Foo" } ")
16+
@Test def hoverOnClass1: Unit = checkActions(code"${"class Foo { } " hover "Foo"}")
17+
18+
@Test def hoverOnValDef0: Unit = checkActions(code"class Foo { ${"val x = " hover "Int"}8 }")
19+
@Test def hoverOnValDef1: Unit = checkActions(code"class Foo { val x = ${"8" hover "Int(8)"} }")
20+
@Test def hoverOnValDef2: Unit = checkActions(code"class Foo { val x = 8; ${"x" hover "Int"} }")
21+
@Test def hoverOnValDef3: Unit = checkActions(code"class Foo { final val x = 8; ${"x" hover "Int(8)"} }")
22+
23+
@Test def hoverOnDefDef0: Unit = checkActions(code"class Foo { ${"def x = " hover "Int"}8 }")
24+
@Test def hoverOnDefDef1: Unit = checkActions(code"class Foo { def x = ${"8" hover "Int(8)"} }")
25+
@Test def hoverOnDefDef2: Unit = checkActions(code"class Foo { def x = 8; ${"x" hover "Int"} }")
26+
@Test def hoverOnDefDef3: Unit = checkActions(code"class Foo { final def x = 8; ${"x" hover "Int"} }")
27+
28+
@Test def hoverMissingRef0: Unit = checkActions(code"class Foo { ${"x" hover "<error not found: x>"} }")
29+
30+
@Test def hoverFun0: Unit = checkActions(
31+
code"""class Foo {
32+
| def x: String = ${"\"abc\"" hover "String(\"abc\")"}
33+
| ${"x" hover "String"}
34+
|
35+
| def y(): Int = 9
36+
| y(${")" hover "Int"}
37+
| ${"y(" hover "(): Int"})
38+
|}
39+
"""
40+
)
41+
42+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package dotty.tools.languageserver
2+
3+
import org.junit.Test
4+
5+
import java.lang.reflect.InvocationTargetException
6+
7+
// TODO remove this and use JUnit to run the tests
8+
object Main {
9+
def main(args: Array[String]): Unit = {
10+
var testsFailed = 0
11+
for (clazz <- testsClasses) {
12+
val t0 = System.currentTimeMillis()
13+
var passed = 0
14+
var failed = 0
15+
println(s"Starting tests in ${clazz.getSimpleName}")
16+
for (method <- clazz.getMethods.sortBy(_.getName)) {
17+
if (method.getAnnotation(classOf[Test]) ne null) {
18+
print(s"Testing $clazz.${method.getName} ")
19+
try {
20+
method.invoke(clazz.getConstructor().newInstance())
21+
println(Console.GREEN + "passed" + Console.RESET)
22+
passed += 1
23+
} catch {
24+
case ex: InvocationTargetException =>
25+
ex.getCause match {
26+
case ex1: AssertionError =>
27+
println(Console.RED + "failed" + Console.RESET)
28+
System.err.println(s"${method.getName} failed with")
29+
System.err.println(ex1.getMessage)
30+
failed += 1
31+
case _ => throw ex
32+
}
33+
}
34+
}
35+
}
36+
37+
val time = (System.currentTimeMillis() - t0).toDouble / 1000
38+
39+
if (failed == 0) {
40+
println(s"${Console.GREEN}Passed all $passed tests${Console.RESET} in ${time}s")
41+
} else {
42+
testsFailed += 1
43+
System.err.println(s"Passed $passed, ${Console.RED}failed $failed${Console.RESET}, total ${passed + failed} in ${time}s")
44+
}
45+
println()
46+
}
47+
if (testsFailed != 0) {
48+
System.err.println(s"Failed $testsFailed tests")
49+
System.exit(1)
50+
}
51+
}
52+
53+
private def testsClasses = List(
54+
classOf[HoverTest],
55+
classOf[DefinitionTest],
56+
classOf[ReferencesTest],
57+
)
58+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package dotty.tools.languageserver
2+
3+
import org.junit.Test
4+
5+
import dotty.tools.languageserver.util.BaseTest
6+
import dotty.tools.languageserver.util.Code._
7+
8+
class ReferencesTest extends BaseTest {
9+
10+
@Test def valReferences0: Unit = {
11+
val source = "X.scala"
12+
val refs = List(source.ref(m1, m2), source.ref(m3, m4))
13+
checkActions(source -> code"class X { val ${"x" references refs} = 9; ${m1}x$m2; ${m3}x$m4 }")
14+
}
15+
16+
@Test def valReferences1: Unit = {
17+
val source = "X.scala"
18+
val refs = List(source.ref(m1, m2), source.ref(m3, m4), source.ref(m5, m6))
19+
val xdef = "x".referencesWithDecl(refs)
20+
checkActions(source -> code"class X { val $m1$xdef$m2 = 9; ${m3}x$m4; ${m5}x$m6 }")
21+
}
22+
23+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package dotty.tools.languageserver.util
2+
3+
import java.nio.file.Path
4+
import java.nio.file.Paths
5+
6+
import dotty.tools.dotc.util.DiffUtil
7+
import dotty.tools.languageserver.util.Code._
8+
import dotty.tools.languageserver.util.actions.{CodeDefinition, CodeHover, CodeMark, CodeReferences}
9+
import dotty.tools.languageserver.util.server.TestServer
10+
11+
class BaseTest {
12+
13+
// Default marks
14+
protected val m1 = new CodeMark
15+
protected val m2 = new CodeMark
16+
protected val m3 = new CodeMark
17+
protected val m4 = new CodeMark
18+
protected val m5 = new CodeMark
19+
protected val m6 = new CodeMark
20+
21+
/** Check all actions performed in the code */
22+
def checkActions(code: CodeWithActions): Unit = checkActions("Foo.scala" -> code)
23+
24+
/** Check all actions performed in the code */
25+
def checkActions(codeInFiles: (String, CodeWithActions)*): Unit = {
26+
27+
val marks = {
28+
val markSeq = {
29+
for {
30+
(fileName, code) <- codeInFiles
31+
(mark, pos) <- code.marks
32+
} yield mark -> (fileName, pos)
33+
}
34+
val markMap = markSeq.toMap
35+
assert(markSeq.size == markMap.size, "Each CodeMark instance can only appear one in the code")
36+
markMap
37+
}
38+
39+
val testServer = new TestServer(BaseTest.testDir)
40+
41+
val allFilesOpened = codeInFiles.map { case (fileName, code) =>
42+
(testServer.openCode(code.text, fileName), code.actions)
43+
}
44+
45+
for {
46+
(file, actions) <- allFilesOpened
47+
action <- actions
48+
if !action.code.isInstanceOf[CodeMark]
49+
(line, character) <- action.range.allPositions
50+
} {
51+
52+
val response = action.code match {
53+
case _: CodeHover => testServer.hover(file, line, character).toString
54+
case _: CodeDefinition => testServer.definition(file, line, character).toString
55+
case codeRef: CodeReferences => testServer.references(file, line, character, codeRef.withDecl).toString
56+
}
57+
58+
val expected = action.code.expected(marks)
59+
60+
if (expected != response) {
61+
62+
val diff = DiffUtil.mkColoredLineDiff(expected.split("\n"), response.split("\n"))
63+
64+
val message = // TODO highlight position in code
65+
s"""When hovering line $line on character $character
66+
|${codeInFiles.map { case (fileName, code) => s"// $fileName\n${code.text}"}.mkString("\n")}
67+
|
68+
|expected output (left) did not match response (right)
69+
|$diff
70+
""".stripMargin
71+
assert(false, message)
72+
}
73+
}
74+
}
75+
76+
}
77+
78+
object BaseTest {
79+
lazy val testDir: Path = Paths.get("../out/ide-tests").toAbsolutePath
80+
lazy val sourceDir: Path = testDir.resolve("src")
81+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package dotty.tools.languageserver.util
2+
3+
import dotty.tools.languageserver.util.actions._
4+
5+
object Code {
6+
7+
implicit class CodeHelper(val sc: StringContext) extends AnyVal {
8+
def code(args: Any*): CodeWithActions = {
9+
val pi = sc.parts.iterator
10+
val ai = args.iterator
11+
12+
var line = 0
13+
var char = 0
14+
def scan(str: String): Unit = {
15+
for (c <- str)
16+
if (c == '\n') { line += 1; char = 0 } else { char += 1 }
17+
}
18+
19+
val stringBuilder = new StringBuilder
20+
val listBuilder = List.newBuilder[ActionOnRange]
21+
val marks = List.newBuilder[(CodeMark, Position)]
22+
23+
while (ai.hasNext) {
24+
val next = pi.next().stripMargin
25+
stringBuilder.append(next)
26+
scan(next)
27+
28+
val startLine = line
29+
val startChar = char
30+
val startPos = Position(startLine, startChar)
31+
32+
ai.next() match {
33+
case mark: CodeMark =>
34+
marks += (mark -> startPos)
35+
case code: CodeWithAction =>
36+
stringBuilder.append(code.text)
37+
scan(code.text)
38+
listBuilder += ActionOnRange(code, actions.Range(startPos, Position(line, char)))
39+
case arg => throw new Exception("Interpolated code should be a CodeWithAction but was " + arg)
40+
}
41+
42+
}
43+
44+
if (pi.hasNext)
45+
stringBuilder.append(pi.next())
46+
47+
CodeWithActions(stringBuilder.result(), listBuilder.result(), marks.result())
48+
}
49+
}
50+
51+
implicit class CodeActions(val str: String) extends AnyVal {
52+
def hover(expected: String): CodeHover = CodeHover(str, expected)
53+
def definition(ref: CodeReference): CodeDefinition = CodeDefinition(str, ref)
54+
def references(refs: List[CodeReference]): CodeReferences = CodeReferences(str, refs, false)
55+
def referencesWithDecl(refs: List[CodeReference]): CodeReferences = CodeReferences(str, refs, true)
56+
def ref(start: CodeMark, end: CodeMark): CodeReference = CodeReference(str, start, end)
57+
}
58+
59+
case class CodeWithActions(text: String, actions: List[ActionOnRange], marks: List[(CodeMark, Position)])
60+
61+
case class ActionOnRange(code: CodeWithAction, range: actions.Range)
62+
63+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dotty.tools.languageserver.util.actions
2+
3+
import dotty.tools.languageserver.util.BaseTest
4+
5+
case class CodeDefinition(text: String, ref: CodeReference) extends CodeWithAction {
6+
override def expected(marks: Map[CodeMark, (String, Position)]): String = {
7+
val (fileName, startPos, endPos) = ref.extract(marks)
8+
s"""List(Location [
9+
| uri = "file://${BaseTest.sourceDir}/$fileName"
10+
| range = Range [
11+
| start = Position [
12+
| line = ${startPos.line}
13+
| character = ${startPos.char}
14+
| ]
15+
| end = Position [
16+
| line = ${endPos.line}
17+
| character = ${endPos.char}
18+
| ]
19+
| ]
20+
|])""".stripMargin
21+
}
22+
}

0 commit comments

Comments
 (0)