diff --git a/app/src/main/assets/data/common/tooling-api-all.jar b/app/src/main/assets/data/common/tooling-api-all.jar
index 5ff75a6be..fc4aeab37 100644
Binary files a/app/src/main/assets/data/common/tooling-api-all.jar and b/app/src/main/assets/data/common/tooling-api-all.jar differ
diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLSPTest.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLSPTest.kt
index 113682c7f..66a22e126 100644
--- a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLSPTest.kt
+++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/JavaLSPTest.kt
@@ -20,6 +20,7 @@ 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.CompilerTest
import com.itsaky.androidide.lsp.java.compiler.JavaCompilerService
import com.itsaky.androidide.lsp.java.partial.PartialReparserImplTester
import com.itsaky.androidide.lsp.java.providers.JavaCompletionProviderTester
@@ -59,6 +60,7 @@ open class JavaLSPTest : LSPTest() {
@Test
override fun test() {
+ CompilerTest().test()
AddImportTester().test()
PartialReparserImplTester().test()
JavaCompletionProviderTester().test()
diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/actions/AddImportTester.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/actions/AddImportTester.kt
index 8a69e982b..4b02ba18c 100644
--- a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/actions/AddImportTester.kt
+++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/actions/AddImportTester.kt
@@ -33,9 +33,12 @@ class AddImportTester : JavaLSPTest() {
openFile("AddImportAction")
val diagnostic =
- server.analyze(file!!).diagnostics.first { it.code == "compiler.err.cant.resolve.location" }
+ server.analyze(file!!).diagnostics.firstOrNull { it.code == "compiler.err.cant.resolve.location" }
+
+ assertThat(diagnostic).isNotNull()
+
val file = this.file!!.toFile()
- val data = createActionData(diagnostic, file, this.file!!, this.server)
+ val data = createActionData(diagnostic!!, file, this.file!!, this.server)
val action = AddImportAction()
action.prepare(data)
diff --git a/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/compiler/CompilerTest.kt b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/compiler/CompilerTest.kt
new file mode 100644
index 000000000..ab3d4260f
--- /dev/null
+++ b/lsp/java/src/test/java/com/itsaky/androidide/lsp/java/compiler/CompilerTest.kt
@@ -0,0 +1,98 @@
+/*
+ * 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 .
+ */
+
+package com.itsaky.androidide.lsp.java.compiler
+
+import com.google.common.truth.Truth.assertThat
+import com.itsaky.androidide.lsp.java.JavaLSPTest
+import java.time.Instant
+
+/** @author Akash Yadav */
+class CompilerTest : JavaLSPTest() {
+
+ override fun test() {
+ testConcurrentAccess()
+ testClosedFileChannel()
+ testMultipleThreads()
+ }
+
+ private fun testMultipleThreads() {
+ openFile("completion/MembersCompletionTest")
+ val threads = mutableListOf()
+ val thread = Thread {
+ getCompiler().compile(file!!).run {
+ println(Thread.currentThread().name)
+ delay(1000)
+ }
+ }
+ thread.name = "Long running task"
+ threads.add(thread)
+ thread.start()
+
+ for (i in 0..300) {
+ val th = Thread {
+ getCompiler().compile(file!!).run {
+ println(Thread.currentThread().name)
+ }
+ }
+ th.name = "Thread #$i"
+ threads.add(th)
+ th.start()
+ }
+
+ threads.forEach { it.join() }
+ }
+
+ private fun testClosedFileChannel() {
+ openFile("completion/MembersCompletionTest")
+
+ Thread { getCompiler().compile(file!!).run { delay(500) } }.start()
+ Thread { getCompiler().compile(file!!).run { delay(200) } }.start()
+
+ getCompiler().compile(file!!).run { assertThat(it.diagnostics).isNotEmpty() }
+ }
+
+ private fun delay(millis: Long) {
+ Thread.sleep(millis)
+ }
+
+ private fun testConcurrentAccess() {
+ openFile("completion/MembersCompletionTest")
+
+ var task = getCompiler().compile(file!!)
+ var fileObject = SourceFileObject(file!!)
+ val threads = mutableListOf()
+ for (i in 1..10) {
+ threads.add(
+ Thread {
+ task.run {
+ delay(100)
+ println(Thread.currentThread())
+ }
+ }
+ .apply { name = "CompileTask Acessor #$i" }
+ )
+ }
+
+ fileObject = SourceFileObject(file!!, fileObject.contents, Instant.now())
+ task = getCompiler().compile(listOf(fileObject))
+ threads.forEach { it.start() }
+
+ Thread { task.run { println("Writer thread") } }.start()
+ threads.forEach { it.join() }
+ }
+}
diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar
index 5991dba6c..e10bcdcf9 100644
Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ