Rewrote Java tests

This commit is contained in:
Akash Yadav
2022-07-10 16:11:33 +05:30
parent e8618ad9a8
commit 2174bfb9bd
39 changed files with 564 additions and 497 deletions
@@ -43,9 +43,7 @@ class ToolingApiImplTest {
fun testProjectInit() {
val client = TestClient()
val (server, project) = ToolingApiTestLauncher().launchServer()
server.initialize(InitializeProjectMessage(File("./src/test/test-project").absolutePath)).get()
verifyProjectProps(project, server)
}
@@ -0,0 +1,7 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
@@ -0,0 +1,7 @@
package com.itsaky.androidide.test;
public class AddImportAction_template {
public static void main(String[] args) {
Stream<String> stream = null;
}
}
@@ -0,0 +1,11 @@
package com.itsaky.androidide.test;
public class GenerateSettersAndGettersTest_template {
private String myString;
private int myInt;
private boolean myBoolean;
public static void main(String[] args) {}
}
@@ -0,0 +1,16 @@
package com.itsaky.androidide.test;
import java.util.function.Consumer;
public class Main {
private void testLambda () {
consume (e -> {
e.@@cursor@@
});
}
private void consume (Consumer<Throwable> consumer) {
consumer.accept (new RuntimeException ("Hello world!"));
}
}
@@ -0,0 +1,11 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
final String aaString = args[0];
final int aaInt = 0;
final float aaFloat = 0f;
final double aaDouble = 0.0;
a@@cursor@@
}
}
@@ -0,0 +1,8 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
String str = new String ();
str.@@cursor@@
}
}
@@ -0,0 +1,7 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
String.@@cursor@@
}
}
@@ -0,0 +1,7 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
@@ -0,0 +1,9 @@
package com.itsaky.androidide.lsp.java.partial;
public class PartialReparserTest_template {
public static void main(String[] args) {
System.out.println("Hello World!");
var str = "";
str.;
}
}
@@ -0,0 +1,8 @@
package com.itsaky.androidide.lsp.java.partial;
public class PartialReparserTest_template {
public static void main(String[] args) {
System.out.println("Hello World!");
var klass = String.class
}
}
@@ -0,0 +1,7 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}
@@ -0,0 +1,7 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
System.out.println(@@cursor@@"Hello World!");
}
}
@@ -0,0 +1,13 @@
package com.itsaky.androidide.test;
public class Main {
public static void main(String[] args) {
try {
// something
} catch (Throwable th) {
// handle error
} finally {
// do something else
}
}
}
@@ -42,7 +42,10 @@ import java.util.concurrent.*
*/
class ToolingApiTestLauncher {
fun launchServer(client: IToolingApiClient = TestClient()): Pair<IToolingApiServer, IProject> {
fun launchServer(
client: IToolingApiClient = TestClient(),
implDir: String = "../tooling-api-impl"
): Pair<IToolingApiServer, IProject> {
val builder =
ProcessBuilder(
"java",
@@ -53,7 +56,7 @@ class ToolingApiTestLauncher {
"--add-opens",
"java.base/java.io=ALL-UNNAMED",
"-jar",
"../tooling-api-impl/build/libs/tooling-api-all.jar"
"$implDir/build/libs/tooling-api-all.jar"
)
val androidHome = findAndroidHome()
println("ANDROID_HOME=$androidHome")
@@ -128,4 +131,4 @@ class ToolingApiTestLauncher {
}
}
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
ext {
androidide = [
javac: 'io.github.itsaky:nb-javac-android:17.0.0.2'
javac: 'io.github.itsaky:nb-javac-android:17.0.0.3-SNAPSHOT'
]
common = [
@@ -19,6 +19,7 @@ package com.itsaky.androidide.lsp.java;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent;
import com.itsaky.androidide.eventbus.events.editor.DocumentSelectedEvent;
@@ -114,7 +115,8 @@ public class JavaLanguageServer implements ILanguageServer {
return settings;
}
protected JavaCompilerService getCompiler(Path file) {
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
public JavaCompilerService getCompiler(Path file) {
if (!DocumentUtils.isJavaFile(file)) {
return JavaCompilerService.NO_MODULE_COMPILER;
}
@@ -47,12 +47,12 @@ class AddImportAction() : BaseCodeAction() {
override fun prepare(data: ActionData) {
super.prepare(data)
if (!visible || !hasRequiredData(data, com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)) {
if (!visible || !hasRequiredData(data, DiagnosticItem::class.java)) {
markInvisible()
return
}
val diagnostic = data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!
val diagnostic = data.get(DiagnosticItem::class.java)!!
if (diagnosticCode != diagnostic.code || diagnostic.extra !is Diagnostic<*>) {
markInvisible()
return
@@ -101,7 +101,7 @@ class AddImportAction() : BaseCodeAction() {
@Suppress("UNCHECKED_CAST")
val diagnostic =
JavaDiagnosticUtils.asUnwrapper(
data.get(com.itsaky.androidide.lsp.models.DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
data.get(DiagnosticItem::class.java)!!.extra as Diagnostic<out JavaFileObject>
)!!
val file = requireFile(data)
val module =
@@ -19,8 +19,8 @@ package com.itsaky.androidide.lsp.java.edits
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.utils.EditHelper
import com.itsaky.androidide.lsp.util.RewriteHelper
import com.itsaky.androidide.lsp.models.CompletionItem
import com.itsaky.androidide.lsp.util.RewriteHelper
import io.github.rosemoe.sora.widget.CodeEditor
import java.nio.file.Path
@@ -36,10 +36,10 @@ class ClassImportEditHandler(val imports: Set<String>, file: Path) : IJavaEditHa
override fun performEdits(
compiler: JavaCompilerService,
editor: CodeEditor,
completionItem: com.itsaky.androidide.lsp.models.CompletionItem
completionItem: CompletionItem
) {
val className = completionItem.data!!.className
val edits = EditHelper.addImportIfNeeded(compiler, file, imports, className)
com.itsaky.androidide.lsp.util.RewriteHelper.performEdits(edits, editor)
RewriteHelper.performEdits(edits, editor)
}
}
@@ -1,27 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java
import com.itsaky.androidide.lsp.api.CursorDependentTest
import com.itsaky.androidide.lsp.api.ILanguageServer
/** @author Akash Yadav */
open class BaseJavaTest : CursorDependentTest() {
protected val mServer = JavaLanguageServerProvider.INSTANCE.server()
override fun getServer(): ILanguageServer = mServer
}
@@ -0,0 +1,67 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java
import com.itsaky.androidide.lsp.api.ILanguageServerRegistry
import com.itsaky.androidide.lsp.api.LSPTest
import com.itsaky.androidide.lsp.java.actions.AddImportTester
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.partial.PartialReparserImplTester
import com.itsaky.androidide.lsp.java.providers.JavaCompletionProviderTester
import com.itsaky.androidide.lsp.java.providers.JavaSelectionProviderTester
import com.itsaky.androidide.projects.ProjectManager
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.DEFAULT_VALUE_STRING)
open class JavaLSPTest : LSPTest() {
protected val server by lazy {
ILanguageServerRegistry.getDefault().getServer(JavaLanguageServer.SERVER_ID)
as JavaLanguageServer
}
@Before
fun setup() {
log.debug("Initializing project...")
initProjectIfNeeded()
}
override fun registerServer() {
ILanguageServerRegistry.getDefault().register(JavaLanguageServer())
}
override fun getServerId() = JavaLanguageServer.SERVER_ID
protected fun getCompiler() : JavaCompilerService {
return JavaCompilerProvider.get(ProjectManager.app)
}
@Test
override fun test() {
AddImportTester().test()
PartialReparserImplTester().test()
JavaCompletionProviderTester().test()
JavaSelectionProviderTester().test()
}
}
@@ -1,33 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java
import com.itsaky.androidide.lsp.api.ILanguageServer
import com.itsaky.androidide.lsp.api.LanguageServerProvider
import com.itsaky.androidide.lsp.java.JavaLanguageServer
class JavaLanguageServerProvider : LanguageServerProvider() {
private val server: JavaLanguageServer = JavaLanguageServer()
override fun getServer(): ILanguageServer {
return server
}
companion object {
@JvmStatic val INSTANCE: LanguageServerProvider = JavaLanguageServerProvider()
}
}
@@ -18,27 +18,24 @@
package com.itsaky.androidide.lsp.java.actions
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.java.BaseJavaTest
import com.itsaky.androidide.lsp.java.JavaLSPTest
import com.itsaky.androidide.lsp.java.actions.diagnostics.AddImportAction
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class AddImportTest : BaseJavaTest() {
class AddImportTester : JavaLSPTest() {
override fun test() {
addImport()
}
@Suppress("UNCHECKED_CAST")
@Test
fun testAddImport() {
fun addImport() {
openFile("AddImportAction")
val diagnostic =
mServer.analyze(file!!).diagnostics.first { it.code == "compiler.err.cant.resolve.location" }
server.analyze(file!!).diagnostics.first { it.code == "compiler.err.cant.resolve.location" }
val file = this.file!!.toFile()
val data = createActionData(diagnostic, file, this.file!!, this.mServer)
val data = createActionData(diagnostic, file, this.file!!, this.server)
val action = AddImportAction()
action.prepare(data)
@@ -18,14 +18,13 @@
package com.itsaky.androidide.lsp.java.partial
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.java.BaseJavaTest
import com.itsaky.androidide.lsp.java.JavaLanguageServer
import com.itsaky.androidide.eventbus.events.editor.ChangeType.INSERT
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent
import com.itsaky.androidide.lsp.java.JavaLSPTest
import com.itsaky.androidide.lsp.java.compiler.SourceFileObject
import com.itsaky.androidide.lsp.java.models.CompilationRequest
import com.itsaky.androidide.lsp.java.models.PartialReparseRequest
import com.itsaky.androidide.lsp.java.visitors.PrintingVisitor
import com.itsaky.androidide.lsp.models.ChangeType.INSERT
import com.itsaky.androidide.lsp.models.DocumentChangeEvent
import com.itsaky.androidide.models.Range
import com.sun.source.tree.ExpressionStatementTree
import com.sun.source.tree.LiteralTree
@@ -36,31 +35,27 @@ import com.sun.tools.javac.tree.JCTree.JCMethodInvocation
import com.sun.tools.javac.tree.JCTree.JCVariableDecl
import com.sun.tools.javac.tree.TreeScanner
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import javax.lang.model.type.ArrayType
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class PartialReparserImplTest : BaseJavaTest() {
// TODO Move this to project ':build-tools:javac-utils'
val jls = mServer as JavaLanguageServer
class PartialReparserImplTester : JavaLSPTest() {
@Test
fun parseMethod() {
override fun test() {
parseMethod()
testSimpleErrorneousStatement()
}
private fun parseMethod() {
openFile("PartialReparserTest")
jls.compiler.compile(file).run { task ->
getCompiler().compile(file).run { task ->
AssertingScanner().scan(task.root() as JCCompilationUnit)
}
}
@Test
fun testSimpleErrorneousStatement() {
private fun testSimpleErrorneousStatement() {
openFile("PartialErrReparserTest")
jls.compiler
getCompiler()
.compile(
CompilationRequest(
listOf(SourceFileObject(file)),
@@ -68,22 +63,23 @@ class PartialReparserImplTest : BaseJavaTest() {
)
)
.run { PrintingVisitor().scan(it.root() as JCCompilationUnit) }
jls.onContentChange(
com.itsaky.androidide.lsp.models.DocumentChangeEvent(
dispatchEvent(
DocumentChangeEvent(
file!!,
contents!!.insert(192, "trim().").toString(),
2,
INSERT,
"trim().".length,
com.itsaky.androidide.models.Range.NONE
Range.NONE
)
)
jls.compiler.compile(
CompilationRequest(
listOf(SourceFileObject(file)),
PartialReparseRequest(179, contents.toString())
getCompiler()
.compile(
CompilationRequest(
listOf(SourceFileObject(file)),
PartialReparseRequest(179, contents.toString())
)
)
)
}
override fun openFile(fileName: String) {
@@ -17,21 +17,20 @@
package com.itsaky.androidide.lsp.java.providers
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.java.BaseJavaTest
import com.itsaky.androidide.lsp.java.JavaLSPTest
import com.itsaky.androidide.lsp.models.CompletionParams
import com.itsaky.androidide.models.Position
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.DEFAULT_VALUE_STRING)
class JavaCompletionProviderTest : BaseJavaTest() {
class JavaCompletionProviderTester : JavaLSPTest() {
@Test
fun testLocals() {
override fun test() {
locals()
members()
staticAccess()
lambdaVariableMemberAccess()
}
fun locals() {
openFile("LocalsCompletionTest")
val pos = cursorPosition()
@@ -39,8 +38,7 @@ class JavaCompletionProviderTest : BaseJavaTest() {
assertThat(items).containsAtLeast("aaString", "aaInt", "aaFloat", "aaDouble", "args")
}
@Test
fun testMembers() {
fun members() {
// Complete members of String
openFile("MembersCompletionTest")
@@ -50,8 +48,7 @@ class JavaCompletionProviderTest : BaseJavaTest() {
.containsAtLeast("getClass", "toLowerCase", "toUpperCase", "substring", "charAt")
}
@Test
fun testLambdaVariableMemberAccess() {
fun lambdaVariableMemberAccess() {
// Complete members of Throwable
openFile("LambdaMembersCompletionTest")
@@ -60,8 +57,7 @@ class JavaCompletionProviderTest : BaseJavaTest() {
assertThat(items).containsAtLeast("getMessage", "getCause", "getStackTrace", "printStackTrace")
}
@Test
fun testStaticAccess() {
fun staticAccess() {
// Complete static members of String
openFile("StaticMembersCompletionTest")
@@ -76,9 +72,8 @@ class JavaCompletionProviderTest : BaseJavaTest() {
}
private fun completionTitles(pos: com.itsaky.androidide.models.Position): List<CharSequence> {
return mServer.completionProvider
.complete(com.itsaky.androidide.lsp.models.CompletionParams(pos, file!!).apply { prefix = "" })
.items
.map { it.label }
return server.complete(CompletionParams(pos, file!!).apply { prefix = "" }).items.map {
it.label
}
}
}
@@ -1,105 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.providers
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.java.BaseJavaTest
import com.itsaky.androidide.lsp.models.ChangeType.NEW_TEXT
import com.itsaky.androidide.lsp.models.DocumentChangeEvent
import com.itsaky.androidide.lsp.models.ExpandSelectionParams
import com.itsaky.androidide.models.Position
import com.itsaky.androidide.models.Range
import io.github.rosemoe.sora.text.Content
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class JavaSelectionProviderTest : BaseJavaTest() {
@Before
fun init() {
LOG.debug("Initializing JavaSelectionProviderTest")
}
@Test
fun testSimpleSelectionExpansion() {
openFile("SimpleSelectionExpansionTest")
cursor = requireCursor()
deleteCursorText()
mServer.documentHandler.onContentChange(
com.itsaky.androidide.lsp.models.DocumentChangeEvent(file!!, contents.toString(), 1, NEW_TEXT, 0, com.itsaky.androidide.models.Range.NONE)
)
val range = findRange()
val expanded = mServer.expandSelection(com.itsaky.androidide.lsp.models.ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(com.itsaky.androidide.models.Range(com.itsaky.androidide.models.Position(4, 27), com.itsaky.androidide.models.Position(4, 41)))
}
@Test
fun testMethodSelection() {
openFile("MethodBodySelectionExpansionTest")
val start = com.itsaky.androidide.models.Position(3, 43)
val end = com.itsaky.androidide.models.Position(5, 5)
val range = com.itsaky.androidide.models.Range(start, end)
val expanded = mServer.expandSelection(com.itsaky.androidide.lsp.models.ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(com.itsaky.androidide.models.Range(com.itsaky.androidide.models.Position(3, 4), end))
}
@Test
fun testTryCatchSelection() {
openFile("TrySelectionExpansionTest")
// Test expand selection if catch block is selected
val start = com.itsaky.androidide.models.Position(7, 10)
val end = com.itsaky.androidide.models.Position(8, 9)
val range = com.itsaky.androidide.models.Range(start, end)
val expanded = mServer.expandSelection(com.itsaky.androidide.lsp.models.ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(com.itsaky.androidide.models.Range(com.itsaky.androidide.models.Position(4, 8), com.itsaky.androidide.models.Position(10, 9)))
}
@Test
fun testTryFinallySelection() {
openFile("TrySelectionExpansionTest")
// Test expand selection if catch block is selected
val start = com.itsaky.androidide.models.Position(8, 18)
val end = com.itsaky.androidide.models.Position(10, 9)
val range = com.itsaky.androidide.models.Range(start, end)
val expanded = mServer.expandSelection(com.itsaky.androidide.lsp.models.ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(com.itsaky.androidide.models.Range(com.itsaky.androidide.models.Position(4, 8), com.itsaky.androidide.models.Position(10, 9)))
}
private fun findRange(): com.itsaky.androidide.models.Range {
val pos = Content(contents!!).indexer.getCharPosition(cursor)
val position = com.itsaky.androidide.models.Position(pos.line, pos.column, pos.index)
return com.itsaky.androidide.models.Range(position, position)
}
override fun openFile(fileName: String) {
super.openFile("selection/${fileName}")
}
}
@@ -0,0 +1,94 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.java.providers
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.eventbus.events.editor.ChangeType.NEW_TEXT
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent
import com.itsaky.androidide.lsp.java.JavaLSPTest
import com.itsaky.androidide.lsp.models.ExpandSelectionParams
import com.itsaky.androidide.models.Position
import com.itsaky.androidide.models.Range
import io.github.rosemoe.sora.text.Content
/** @author Akash Yadav */
class JavaSelectionProviderTester : JavaLSPTest() {
override fun test() {
testSimpleSelectionExpansion()
testMethodSelection()
testTryCatchSelection()
testTryFinallySelection()
}
private fun testSimpleSelectionExpansion() {
openFile("SimpleSelectionExpansionTest")
cursor = requireCursor()
deleteCursorText()
dispatchEvent(DocumentChangeEvent(file!!, contents.toString(), 1, NEW_TEXT, 0, Range.NONE))
val range = findRange()
val expanded = server.expandSelection(ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(Range(Position(4, 27), Position(4, 41)))
}
private fun testMethodSelection() {
openFile("MethodBodySelectionExpansionTest")
val start = Position(3, 43)
val end = Position(5, 5)
val range = Range(start, end)
val expanded = server.expandSelection(ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(Range(Position(3, 4), end))
}
private fun testTryCatchSelection() {
openFile("TrySelectionExpansionTest")
// Test expand selection if catch block is selected
val start = Position(7, 10)
val end = Position(8, 9)
val range = Range(start, end)
val expanded = server.expandSelection(ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(Range(Position(4, 8), Position(10, 9)))
}
private fun testTryFinallySelection() {
openFile("TrySelectionExpansionTest")
// Test expand selection if catch block is selected
val start = Position(8, 18)
val end = Position(10, 9)
val range = Range(start, end)
val expanded = server.expandSelection(ExpandSelectionParams(file!!, range))
assertThat(expanded).isEqualTo(Range(Position(4, 8), Position(10, 9)))
}
private fun findRange(): Range {
val pos = Content(contents!!).indexer.getCharPosition(cursor)
val position = Position(pos.line, pos.column, pos.index)
return Range(position, position)
}
override fun openFile(fileName: String) {
super.openFile("selection/${fileName}")
}
}
+2
View File
@@ -51,6 +51,8 @@ dependencies {
implementation project (':lsp:api')
implementation project (':actions')
implementation project (':common')
implementation project (':build-tools:eventbus-android')
implementation project (':build-tools:tooling-api-testing')
implementation common.editor
implementation tests.junit
implementation tests.google_truth
@@ -1,53 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.api
import android.content.Context
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.lsp.models.DocumentOpenEvent
import org.robolectric.RuntimeEnvironment
import java.nio.file.Path
/** @author Akash Yadav */
abstract class BaseLanguageServerTest {
protected var file: Path? = null
protected var contents: StringBuilder? = null
open fun openFile(fileName: String) {
file = FileProvider.sourceFile(fileName)
contents = FileProvider.contents(file!!)
getServer().documentHandler.onFileOpened(com.itsaky.androidide.lsp.models.DocumentOpenEvent(file!!, contents.toString(), 0))
}
protected open fun createActionData(vararg values: Any): ActionData {
val data = ActionData()
data.put(Context::class.java, RuntimeEnvironment.getApplication())
for (value in values) {
if (value is Path) {
data.put(Path::class.java, value)
} else {
data.put(value::javaClass.get(), value)
}
}
return data
}
protected abstract fun getServer(): ILanguageServer
}
@@ -1,66 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.api
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.lsp.models.ChangeType.NEW_TEXT
import com.itsaky.androidide.lsp.models.DocumentChangeEvent
import com.itsaky.androidide.models.Position
import com.itsaky.androidide.models.Range
import io.github.rosemoe.sora.text.Content
/**
* A cursor dependent test is a test that needs to have cursor positions specified in the content.
* `@@cursor@@` denotes the position of the cursor in source files.
*
* @author Akash Yadav
*/
abstract class CursorDependentTest : LoggingTest() {
protected var cursor: Int = -1
private val cursorText = "@@cursor@@"
fun requireCursor(): Int {
this.cursor = super.contents!!.indexOf(cursorText)
assertThat(cursor).isGreaterThan(-1)
return cursor
}
fun deleteCursorText() {
contents!!.delete(this.cursor, this.cursor + cursorText.length)
assertThat(contents!!.indexOf(cursorText)).isEqualTo(-1)
// As the content has been changed, we have to
// Update the content in language server
getServer().documentHandler.onContentChange(com.itsaky.androidide.lsp.models.DocumentChangeEvent(file!!, contents!!.toString(), 1, NEW_TEXT, 0, com.itsaky.androidide.models.Range.NONE))
}
fun cursorPosition(): com.itsaky.androidide.models.Position {
return cursorPosition(true)
}
fun cursorPosition(deleteCursorText: Boolean): com.itsaky.androidide.models.Position {
requireCursor()
if (deleteCursorText) {
deleteCursorText()
}
val pos = Content(contents!!).indexer.getCharPosition(cursor)
return com.itsaky.androidide.models.Position(pos.line, pos.column, pos.index)
}
}
@@ -16,10 +16,7 @@
*/
package com.itsaky.androidide.lsp.api
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.readText
@@ -30,34 +27,6 @@ import kotlin.io.path.readText
* @author Akash Yadav
*/
class FileProvider {
@Test
fun testPath() {
val path = sourceFile("SourceFileTest")
assertThat(Files.exists(path)).isTrue()
assertThat(path.fileName.toString()).isEqualTo("SourceFileTest.java")
}
@Test
fun testNested() {
val path = sourceFile("package/SourceFileTest")
assertThat(Files.exists(path)).isTrue()
assertThat(path.fileName.toString()).isEqualTo("SourceFileTest.java")
}
@Test
fun testExtension() {
val folder = File(".").canonicalFile
assertThat(extension).isNotEmpty()
assertThat(extension)
.isEqualTo(
when (folder.name) {
"xml" -> "xml"
"java" -> "java"
else -> ""
}
)
}
companion object {
@@ -71,6 +40,11 @@ class FileProvider {
}
}
@JvmStatic
fun implModule(): Path =
Paths.get(System.getProperty("user.dir")!!).resolve("../../build-tools/tooling-api-impl")
@JvmStatic fun projectRoot(): Path = implModule().resolve("src/test/test-project")
/**
* Get the path to the 'resources' directory.
*
@@ -78,7 +52,7 @@ class FileProvider {
*/
@JvmStatic
fun resources(): Path {
return Paths.get(".", "src", "test", "resources")
return projectRoot().resolve("app/src/main/resources")
}
/**
@@ -0,0 +1,152 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.api
import android.content.Context
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.eventbus.events.editor.ChangeType.NEW_TEXT
import com.itsaky.androidide.eventbus.events.editor.DocumentChangeEvent
import com.itsaky.androidide.eventbus.events.editor.DocumentOpenEvent
import com.itsaky.androidide.lsp.api.util.ReflectiveLogListener
import com.itsaky.androidide.models.Range
import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.tooling.api.IProject
import com.itsaky.androidide.tooling.api.IToolingApiServer
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.ILogger.Priority
import io.github.rosemoe.sora.text.Content
import java.nio.file.Path
import java.util.*
import kotlin.io.path.pathString
import org.greenrobot.eventbus.EventBus
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import java.io.File
/**
* Runs tests for a language server.
*
* @author Akash Yadav
*/
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.DEFAULT_VALUE_STRING)
abstract class LSPTest {
protected lateinit var toolingServer: IToolingApiServer
protected lateinit var toolingProject: IProject
protected var cursor: Int = -1
private val cursorText = "@@cursor@@"
protected var file: Path? = null
protected var contents: StringBuilder? = null
protected val log = ILogger.newInstance(javaClass.simpleName)
companion object {
@JvmStatic
protected var isInitialized: Boolean = false
}
protected open fun initProjectIfNeeded() {
if (isInitialized) {
return
}
val (server, project) =
ToolingApiTestLauncher().launchServer(implDir = FileProvider.implModule().pathString)
this.toolingProject = project
this.toolingServer = server
server
.initialize(InitializeProjectMessage(FileProvider.projectRoot().toFile().absolutePath))
.get()
Environment.ANDROID_JAR = FileProvider.resources().resolve("android.jar").toFile()
Environment.JAVA_HOME = File(System.getProperty("java.home")!!)
registerServer()
ProjectManager.register()
ProjectManager.setupProject(project)
ProjectManager.notifyProjectUpdate()
isInitialized = true
}
protected abstract fun registerServer()
protected abstract fun getServerId(): String
abstract fun test()
protected fun requireCursor(): Int {
this.cursor = contents!!.indexOf(cursorText)
assertThat(cursor).isGreaterThan(-1)
return cursor
}
protected fun deleteCursorText() {
contents!!.delete(this.cursor, this.cursor + cursorText.length)
assertThat(contents!!.indexOf(cursorText)).isEqualTo(-1)
// As the content has been changed, we have to
// Update the content in language server
dispatchEvent(DocumentChangeEvent(file!!, contents.toString(), 1, NEW_TEXT, 0, Range.NONE))
}
protected fun cursorPosition(): com.itsaky.androidide.models.Position {
return cursorPosition(true)
}
protected fun cursorPosition(deleteCursorText: Boolean): com.itsaky.androidide.models.Position {
requireCursor()
if (deleteCursorText) {
deleteCursorText()
}
val pos = Content(contents!!).indexer.getCharPosition(cursor)
return com.itsaky.androidide.models.Position(pos.line, pos.column, pos.index)
}
protected open fun openFile(fileName: String) {
file = FileProvider.sourceFile(fileName).normalize()
contents = FileProvider.contents(file!!)
dispatchEvent(DocumentOpenEvent(file!!, contents.toString(), 0))
}
protected open fun dispatchEvent(event: Any) {
EventBus.getDefault().post(event)
}
protected open fun createActionData(vararg values: Any): ActionData {
val data = ActionData()
data.put(Context::class.java, RuntimeEnvironment.getApplication())
for (value in values) {
if (value is Path) {
data.put(Path::class.java, value)
} else {
data.put(value::javaClass.get(), value)
}
}
return data
}
}
@@ -1,59 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.api
import com.google.common.truth.Truth.assertThat
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.lsp.api.FileProvider.Companion.resources
import com.itsaky.androidide.lsp.models.InitializeParams
import org.jetbrains.annotations.Contract
import java.io.File
/**
* Provides instance to the java language server to test classes.
*
* @author Akash Yadav
*/
abstract class LanguageServerProvider {
fun server(): ILanguageServer {
// TODO replace with android.jar
// if (Environment.COMPILER_MODULE == null) {
// val javaHome = System.getProperty("java.home")
// assertThat(javaHome).isNotEmpty()
// Environment.COMPILER_MODULE = File(javaHome)
// }
initIfNecessary()
return getServer()
}
protected abstract fun getServer(): ILanguageServer
protected open fun initIfNecessary() {
if (!getServer().isInitialized) {
getServer().initialize(createInitParams())
}
}
@Contract(" -> new")
private fun createInitParams(): com.itsaky.androidide.lsp.models.InitializeParams {
return com.itsaky.androidide.lsp.models.InitializeParams(mutableSetOf(resources()))
}
}
@@ -1,47 +0,0 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.api
import com.itsaky.androidide.utils.ILogger
import com.itsaky.androidide.utils.ILogger.Priority
import com.itsaky.androidide.lsp.api.util.ReflectiveLogListener
import java.util.*
/**
* This class provides an efficient way to log messages.
*
* @author Akash Yadav
*/
abstract class LoggingTest : BaseLanguageServerTest() {
companion object {
private var logListener: ReflectiveLogListener? = null
init {
logListener = ReflectiveLogListener(LoggingTest::class.java)
ILogger.addLogListener(logListener)
}
@Suppress("unused")
@JvmStatic
fun log(priority: Priority, tag: String, message: String) {
System.out.printf(Locale.ROOT, "%-8s%-26s%-20s", priority, tag, message)
println()
}
}
@Suppress("PropertyName") @JvmField protected val LOG = ILogger.newInstance(javaClass.simpleName)
}
@@ -0,0 +1,61 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.api
import com.google.common.truth.Truth
import java.io.File
import java.nio.file.Files
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class FileProviderTest {
@Test
fun testPath() {
val path = FileProvider.sourceFile("SourceFileTest")
Truth.assertThat(Files.exists(path)).isTrue()
Truth.assertThat(path.fileName.toString()).isEqualTo("SourceFileTest.java")
}
@Test
fun testNested() {
val path = FileProvider.sourceFile("package/SourceFileTest")
Truth.assertThat(Files.exists(path)).isTrue()
Truth.assertThat(path.fileName.toString()).isEqualTo("SourceFileTest.java")
}
@Test
fun testExtension() {
val folder = File(".").canonicalFile
Truth.assertThat(FileProvider.extension).isNotEmpty()
Truth.assertThat(FileProvider.extension)
.isEqualTo(
when (folder.name) {
"xml" -> "xml"
"java" -> "java"
else -> ""
}
)
}
}
@@ -18,8 +18,6 @@
package com.itsaky.androidide.lsp.xml
import com.itsaky.androidide.lsp.api.ILanguageServer
import com.itsaky.androidide.lsp.api.LanguageServerProvider
import com.itsaky.androidide.lsp.xml.XMLLanguageServer
import com.itsaky.sdk.SDKInfo
import org.robolectric.RuntimeEnvironment
@@ -72,7 +72,7 @@ class XMLCompletionProviderTest : BaseXMLTest() {
private fun complete(): Pair<Boolean, List<String>> {
val createCompletionParams = createCompletionParams()
val result = mServer.completionProvider.complete(createCompletionParams)
val result = mServer.complete(createCompletionParams)
return result.isIncomplete to
result.items
.filter { it.label != null }