tests: use FileProvider API to access file paths

This commit is contained in:
Akash Yadav
2023-04-09 23:22:16 +05:30
parent 23c30eeaeb
commit 7ddd2f5a82
22 changed files with 202 additions and 87 deletions
+3
View File
@@ -0,0 +1,3 @@
DO NOT DELETE!
This file is used in tests to check for the project's root directory.
Binary file not shown.
+2 -1
View File
@@ -33,10 +33,11 @@ dependencies {
implementation(projects.common)
implementation(projects.eventbusAndroid)
implementation(projects.subprojects.toolingApiTesting)
implementation(projects.shared)
implementation(libs.common.editor)
implementation(libs.tests.junit)
implementation(libs.tests.google.truth)
implementation(libs.tests.robolectric)
implementation(libs.tests.mockk)
implementation(projects.actions)
}
@@ -39,19 +39,20 @@ import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher.MultiVersionTestClient
import com.itsaky.androidide.utils.Environment
import com.itsaky.androidide.utils.FileProvider
import com.itsaky.androidide.utils.ILogger
import io.github.rosemoe.sora.text.Content
import io.mockk.every
import io.mockk.mockkStatic
import java.io.File
import java.nio.file.Path
import kotlin.io.path.pathString
import org.greenrobot.eventbus.EventBus
import org.junit.Before
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config
import java.io.File
import java.nio.file.Path
import kotlin.io.path.pathString
/**
* Runs tests for a language server.
@@ -87,7 +88,6 @@ abstract class LSPTest {
val (server, project) =
ToolingApiTestLauncher()
.launchServer(
implDir = FileProvider.implModule().pathString,
client = MultiVersionTestClient()
)
this.toolingProject = project
@@ -96,7 +96,7 @@ abstract class LSPTest {
Lookup.DEFAULT.update(BuildService.KEY_PROJECT_PROXY, project)
server
.initialize(InitializeProjectMessage(FileProvider.projectRoot().toFile().absolutePath))
.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().toFile().absolutePath))
.get()
Environment.ANDROID_JAR = FileProvider.resources().resolve("android.jar").toFile()
@@ -130,7 +130,17 @@ abstract class LSPTest {
// As the content has been changed, we have to
// Update the content in language server
dispatchEvent(DocumentChangeEvent(file!!, contents.toString(), contents.toString(), 1, DELETE, 0, Range.NONE))
dispatchEvent(
DocumentChangeEvent(
file!!,
contents.toString(),
contents.toString(),
1,
DELETE,
0,
Range.NONE
)
)
}
@JvmOverloads
+3
View File
@@ -25,4 +25,7 @@ dependencies {
implementation(libs.androidx.annotation)
implementation(libs.google.guava)
implementation(projects.logger)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.junit)
}
@@ -14,15 +14,21 @@
* 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
package com.itsaky.androidide.utils
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.absolute
import kotlin.io.path.name
import kotlin.io.path.readText
import kotlin.io.path.walk
const val PROJECT_ROOT_FILE = ".androidide_root"
/**
* Provides paths to source files in 'resources' directory.
* Provides paths to various directories and files.
*
* @author Akash Yadav
*/
@@ -30,6 +36,10 @@ class FileProvider {
companion object {
private val projectRoot by lazy {
currentDir().findProjectRoot()?.absolute()?.normalize()
}
@JvmField
val extension = run {
val file = File(".").canonicalFile
@@ -41,8 +51,15 @@ class FileProvider {
@JvmStatic fun currentDir(): Path = Paths.get(System.getProperty("user.dir")!!)
@JvmStatic fun implModule(): Path = currentDir().resolve("../../subprojects/tooling-api-impl")
@JvmStatic fun projectRoot(): Path = currentDir().resolve("../../tests/test-project")
@JvmStatic fun implModule(): Path = projectRoot().resolve("subprojects/tooling-api-impl")
@JvmStatic
fun projectRoot(): Path =
checkNotNull(projectRoot) {
"Unable to file project root. Check if '${PROJECT_ROOT_FILE}' file exists in the project root directory."
}
@JvmStatic fun testProjectRoot(): Path = projectRoot().resolve("tests/test-project")
/**
* Get the path to the 'resources' directory.
@@ -51,7 +68,7 @@ class FileProvider {
*/
@JvmStatic
fun resources(): Path {
return projectRoot().resolve("app/src/main/resources")
return testProjectRoot().resolve("app/src/main/resources")
}
/**
@@ -68,3 +85,12 @@ class FileProvider {
@JvmStatic fun contents(file: Path): StringBuilder = StringBuilder(file.readText())
}
}
@OptIn(ExperimentalPathApi::class)
private fun Path.findProjectRoot(): Path? {
if (walk().find { it.name == PROJECT_ROOT_FILE } != null) {
return this
}
return parent?.findProjectRoot()
}
@@ -15,41 +15,35 @@
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.lsp.api
package com.itsaky.androidide.utils
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import org.junit.Test
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_template.java")
assertThat(Files.exists(path)).isTrue()
assertThat(path.fileName.toString()).isEqualTo("SourceFileTest_template.java")
}
@Test
fun testNested() {
val path = FileProvider.sourceFile("package/SourceFileTest")
Truth.assertThat(Files.exists(path)).isTrue()
Truth.assertThat(path.fileName.toString()).isEqualTo("SourceFileTest_template.java")
assertThat(Files.exists(path)).isTrue()
assertThat(path.fileName.toString()).isEqualTo("SourceFileTest_template.java")
}
@Test
fun testExtension() {
val folder = File(".").canonicalFile
Truth.assertThat(FileProvider.extension).isNotEmpty()
Truth.assertThat(FileProvider.extension)
assertThat(FileProvider.extension).isNotEmpty()
assertThat(FileProvider.extension)
.isEqualTo(
when (folder.name) {
"xml" -> "xml"
@@ -57,4 +51,4 @@ class FileProviderTest {
}
)
}
}
}
@@ -44,4 +44,5 @@ dependencies {
testImplementation(libs.tests.junit)
testImplementation(libs.tests.robolectric)
testImplementation(libs.tests.google.truth)
testImplementation(projects.shared)
}
@@ -26,6 +26,7 @@ import com.android.aaptcompiler.compileResource
import com.android.aaptcompiler.extractPathData
import com.android.utils.StdLogger
import com.android.utils.StdLogger.Level.VERBOSE
import com.itsaky.androidide.utils.FileProvider
import java.nio.file.Paths
import kotlin.io.path.absolute
import org.junit.Test
@@ -39,7 +40,7 @@ class CompilerTest {
@Test
fun `test simple compilatation`() {
val input =
Paths.get("../../app/src/main/res/layout/activity_editor.xml").absolute().normalize().toFile()
FileProvider.projectRoot().resolve("app/src/main/res/layout/activity_editor.xml").toFile()
val output = Paths.get("./build").absolute().normalize().toFile()
println(input)
println(output)
+1
View File
@@ -34,6 +34,7 @@ dependencies {
implementation(libs.google.guava)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(projects.shared)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
@@ -26,15 +26,16 @@ import com.itsaky.androidide.projects.ProjectManager
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
import com.itsaky.androidide.utils.FileProvider
import com.itsaky.androidide.utils.SourceClassTrie.SourceNode
import java.io.File
import java.nio.file.Files
import kotlin.io.path.pathString
import kotlin.io.path.writeText
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.writeText
/** @author Akash Yadav */
@RunWith(RobolectricTestRunner::class)
@@ -44,7 +45,7 @@ class ModuleProjectTest {
@Test
fun test() {
val (server, project) = ToolingApiTestLauncher().launchServer()
server.initialize(InitializeProjectMessage(File("../../tests/test-project").absolutePath)).get()
server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get()
Lookup.DEFAULT.register(BuildService.KEY_PROJECT_PROXY, project)
@@ -56,8 +57,7 @@ class ModuleProjectTest {
val rootDir = root.projectDir
assertThat(rootDir).isNotNull()
assertThat(Files.isSameFile(rootDir.toPath(), File("../../tests/test-project").toPath()))
.isTrue()
assertThat(Files.isSameFile(rootDir.toPath(), FileProvider.testProjectRoot())).isTrue()
val app = root.findByPath(":app")
assertThat(app).isNotNull()
@@ -183,7 +183,8 @@ class ModuleProjectTest {
private fun verifyProjectManagerAPIs() {
val testCls =
Paths.get("../../tests/test-project/app/src/main/java/com/itsaky/test/app/Test.java")
FileProvider.testProjectRoot()
.resolve("app/src/main/java/com/itsaky/test/app/Test.java")
.toAbsolutePath()
.normalize()
val testCls_renamed = testCls.parent!!.resolve("TestRenamed.java")
@@ -18,7 +18,7 @@
package com.itsaky.androidide.projects.classpath
import com.google.common.truth.Truth.assertThat
import java.io.File
import com.itsaky.androidide.utils.FileProvider
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
@@ -32,9 +32,12 @@ class ZipFileClasspathReaderTest {
@Test
fun testListClasses() {
val classes =
ZipFileClasspathReader().listClasses(
listOf(File("../../tests/test-project/app/src/main/resources/android.jar"))
)
ZipFileClasspathReader()
.listClasses(
listOf(
FileProvider.testProjectRoot().resolve("app/src/main/resources/android.jar").toFile()
)
)
val context = classes.firstOrNull { it.name == "android.content.Context" }
assertThat(context).isNotNull()
@@ -63,6 +63,7 @@ dependencies {
implementation(libs.tooling.gradleApi)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(projects.shared)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
@@ -30,11 +30,12 @@ import com.itsaky.androidide.tooling.api.model.JavaModuleExternalDependency
import com.itsaky.androidide.tooling.api.model.JavaModuleProjectDependency
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher.MultiVersionTestClient
import com.itsaky.androidide.utils.FileProvider
import kotlin.io.path.deleteExisting
import kotlin.io.path.pathString
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import java.io.File
import kotlin.jvm.Throws
/** @author Akash Yadav */
@RunWith(JUnit4::class)
@@ -42,8 +43,8 @@ class MultiModuleAndroidProjectTest {
@Test
fun `test simple multi module project initialization`() {
val (server, project) = ToolingApiTestLauncher().launchServer(client = MultiVersionTestClient("7.2.0"))
server.initialize(InitializeProjectMessage(File("../../tests/test-project").absolutePath)).get()
val (server, project) = ToolingApiTestLauncher().launchServer()
server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get()
doAssertions(project, server)
}
@@ -148,37 +149,35 @@ class MultiModuleAndroidProjectTest {
val versions = listOf("7.2.0", "7.2.1", "7.2.2", "7.3.0", "7.4.0", "8.0.0-rc01")
val client = MultiVersionTestClient()
for (version in versions) {
client.version = version
client.agpVersion = version
val (server, project) = ToolingApiTestLauncher().launchServer(client = client)
server
.initialize(InitializeProjectMessage(File("../../tests/test-project").absolutePath))
.get()
server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get()
doAssertions(project = project, server = server)
MultiVersionTestClient.buildFile.delete()
FileProvider.testProjectRoot().resolve(MultiVersionTestClient.buildFile).deleteExisting()
}
}
}
private fun ciOnlyTest(test: () -> Unit) {
try {
assertIsCI()
test()
} catch (err: CIOnlyException) {
if(shouldTestMultipleVersions()) {
if (shouldTestMultipleVersions()) {
throw err
}
}
}
private fun assertIsCI() {
if (!shouldTestMultipleVersions()) {
throw CIOnlyException()
}
}
private fun shouldTestMultipleVersions() : Boolean {
private fun shouldTestMultipleVersions(): Boolean {
return System.getenv("TEST_TOOLING_API_IMPL").let { it == "true" }
}
private class CIOnlyException : IllegalStateException()
}
@@ -25,7 +25,8 @@ dependencies {
implementation(libs.common.jkotlin)
implementation(projects.logger)
implementation(projects.subprojects.toolingApi)
implementation(projects.shared)
// build tooling API before tests
compileOnly(projects.subprojects.toolingApiImpl)
}
@@ -25,13 +25,19 @@ import com.itsaky.androidide.tooling.api.messages.result.BuildResult
import com.itsaky.androidide.tooling.api.messages.result.GradleWrapperCheckResult
import com.itsaky.androidide.tooling.api.util.ToolingApiLauncher
import com.itsaky.androidide.tooling.events.ProgressEvent
import com.itsaky.androidide.utils.FileProvider
import com.itsaky.androidide.utils.ILogger
import java.io.BufferedReader
import java.io.File
import java.io.InputStream
import java.io.InputStreamReader
import java.nio.file.Path
import java.util.Collections
import java.util.concurrent.CompletableFuture
import kotlin.io.path.bufferedReader
import kotlin.io.path.bufferedWriter
import kotlin.io.path.deleteIfExists
import kotlin.io.path.pathString
/**
* Launches the Tooling API server in a separate JVM process.
@@ -46,7 +52,7 @@ class ToolingApiTestLauncher {
private val opens =
mutableMapOf("java.base" to "java.lang", "java.base" to "java.util", "java.base" to "java.io")
private val exports =
mutableMapOf(
"jdk.compiler" to "com.sun.tools.javac.api",
@@ -57,10 +63,14 @@ class ToolingApiTestLauncher {
)
fun launchServer(
client: IToolingApiClient = TestClient(),
implDir: String = "../tooling-api-impl"
client: IToolingApiClient = MultiVersionTestClient(),
): Pair<IToolingApiServer, IProject> {
val builder = ProcessBuilder(createProcessCmd("$implDir/build/libs/tooling-api-all.jar"))
val builder =
ProcessBuilder(
createProcessCmd(
FileProvider.implModule().resolve("build/libs/tooling-api-all.jar").pathString
)
)
val androidHome = findAndroidHome()
println("ANDROID_HOME=$androidHome")
builder.environment()["ANDROID_SDK_ROOT"] = androidHome
@@ -86,9 +96,9 @@ class ToolingApiTestLauncher {
}
Collections.addAll(cmd, "-jar", jar)
println("[ToolingApiTestLauncher] Java cmd: " + cmd.joinToString(separator = " "))
return cmd
}
@@ -112,8 +122,26 @@ class ToolingApiTestLauncher {
}
}
open class TestClient : IToolingApiClient {
private val log = ILogger.newInstance(javaClass.simpleName)
class MultiVersionTestClient(
private val projectDir: Path = FileProvider.testProjectRoot(),
var agpVersion: String = DEFAULT_AGP_VERSION,
var gradleVersion: String = DEFAULT_GRADLE_VERSION
) : IToolingApiClient {
companion object {
const val buildFile = "build.gradle"
const val buildFileIn = "$buildFile.in"
const val gradlewProps = "gradle/wrapper/gradle-wrapper.properties"
const val gradlewPropsIn = "$gradlewProps.in"
const val DEFAULT_AGP_VERSION = "7.2.0"
const val DEFAULT_GRADLE_VERSION = "7.5.1"
const val GENERATED_FILE_WARNING = "DO NOT EDIT - Automatically generated file"
private val log = ILogger.newInstance("MultiVersionTestClient")
}
override fun logMessage(line: LogLine) {
log.log(line.priority, line.formattedTagAndMessage())
}
@@ -122,7 +150,27 @@ class ToolingApiTestLauncher {
log.debug(line.trim())
}
override fun prepareBuild() {}
override fun prepareBuild() {
log.debug("-----------------------------------")
log.debug("---------- PREPARE BUILD ----------")
log.debug("-----------------------------------")
projectDir
.resolve(buildFileIn)
.replaceContents(
dest = projectDir.resolve(buildFile),
candidate = "@@TOOLING_API_TEST_AGP_VERSION@@" to this.agpVersion
)
projectDir
.resolve(gradlewPropsIn)
.replaceContents(
comment = "#",
dest = projectDir.resolve(gradlewProps),
candidate = "@@GRADLE_VERSION@@" to this.gradleVersion
)
}
override fun onBuildSuccessful(result: BuildResult) {}
override fun onBuildFailed(result: BuildResult) {}
@@ -134,24 +182,40 @@ class ToolingApiTestLauncher {
override fun checkGradleWrapperAvailability(): CompletableFuture<GradleWrapperCheckResult> =
CompletableFuture.completedFuture(GradleWrapperCheckResult(true))
}
class MultiVersionTestClient(var version: String = "7.2.0") : TestClient() {
private fun Path.replaceContents(
dest: Path,
comment: String = "//",
candidate: Pair<String, String>
) = replaceContents(dest, comment, *arrayOf(candidate))
companion object {
val buildTemplateFile = File("../../tests/test-project/build.gradle.in")
val buildFile = File(buildTemplateFile.parentFile, "build.gradle")
}
private fun Path.replaceContents(
dest: Path,
comment: String = "//",
vararg candidates: Pair<String, String>
) {
val contents =
StringBuilder()
.append(comment)
.append(" ")
.append(GENERATED_FILE_WARNING)
.append(System.getProperty("line.separator").repeat(2))
override fun prepareBuild() {
super.prepareBuild()
var contents = buildTemplateFile.bufferedReader().readText()
contents = contents.replace("@@TOOLING_API_TEST_AGP_VERSION@@", this.version)
contents = "/* DO NOT EDIT - Automatically generated file */\n${contents.trim()}"
val writer = buildFile.bufferedWriter()
writer.use {
it.write(contents)
it.flush()
bufferedReader().use { reader ->
reader.readText().also { text ->
var t = text
for ((old, new) in candidates) {
t = t.replace(old, new)
}
contents.append(t)
}
}
dest.deleteIfExists()
dest.bufferedWriter().use { writer ->
writer.write(contents.toString())
writer.flush()
}
}
}
+2 -1
View File
@@ -1 +1,2 @@
build/
/build.gradle
build/
+3 -2
View File
@@ -1,4 +1,5 @@
/* DO NOT EDIT - Automatically generated file */
// DO NOT EDIT - Automatically generated file
buildscript {
ext {
build_gradle_version = "7.2.0"
@@ -34,4 +35,4 @@ allprojects {
task clean (type: Delete) {
delete rootProject.buildDir
}
}
@@ -0,0 +1 @@
gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-@@GRADLE_VERSION@@-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
+1
View File
@@ -42,6 +42,7 @@ dependencies {
testImplementation(projects.subprojects.projects)
testImplementation(projects.subprojects.toolingApiTesting)
testImplementation(projects.shared)
testImplementation(libs.tests.junit)
testImplementation(libs.tests.google.truth)
testImplementation(libs.tests.robolectric)
@@ -26,9 +26,11 @@ import com.itsaky.androidide.projects.api.AndroidModule
import com.itsaky.androidide.projects.builder.BuildService
import com.itsaky.androidide.tooling.api.messages.InitializeProjectMessage
import com.itsaky.androidide.tooling.testing.ToolingApiTestLauncher
import com.itsaky.androidide.utils.FileProvider
import java.io.File
import org.junit.Ignore
import org.robolectric.Robolectric
import kotlin.io.path.pathString
@Ignore("Test utility provider")
object XmlInflaterTest {
@@ -42,8 +44,8 @@ object XmlInflaterTest {
}
val (server, project) =
ToolingApiTestLauncher().launchServer(implDir = "../subprojects/tooling-api-impl")
server.initialize(InitializeProjectMessage(File("../tests/test-project").absolutePath)).get()
ToolingApiTestLauncher().launchServer()
server.initialize(InitializeProjectMessage(FileProvider.testProjectRoot().pathString)).get()
Lookup.DEFAULT.register(BuildService.KEY_PROJECT_PROXY, project)
ProjectManager.setupProject()