mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
fix(lsp/java): add parser for parsing api-versions.xml
This commit is contained in:
@@ -64,13 +64,15 @@ dependencies {
|
||||
implementation(projects.subprojects.javacServices)
|
||||
|
||||
implementation(libs.composite.javac)
|
||||
implementation(libs.composite.jdkJdeps)
|
||||
implementation(libs.composite.javapoet)
|
||||
implementation(libs.composite.jaxp)
|
||||
implementation(libs.composite.jdkJdeps)
|
||||
implementation(libs.composite.googleJavaFormat)
|
||||
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.common.kotlin)
|
||||
|
||||
testImplementation(projects.testing.common)
|
||||
testImplementation(projects.testing.lsp)
|
||||
androidTestImplementation(projects.testing.android)
|
||||
androidTestImplementation(projects.common)
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.lsp.java.indexing
|
||||
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationAnnotationElementValue
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationElement
|
||||
import com.itsaky.androidide.lsp.java.indexing.classfile.AnnotationElement.Companion.newAnnotationElement
|
||||
@@ -68,6 +69,7 @@ class JavaJarModelBuilder(private val jar: File) {
|
||||
|
||||
// for testing purposes
|
||||
// prefer using consumeTypes(Function)
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
fun buildTypes(): List<IJavaType<*, *>> {
|
||||
val types = mutableListOf<IJavaType<*, *>>()
|
||||
consumeTypes { types.add(it) }
|
||||
|
||||
@@ -41,13 +41,16 @@ open class ApiInfo : ISharedJavaIndexable, ICloneable {
|
||||
override var id: Int? = null
|
||||
|
||||
@RealmField("since")
|
||||
private var since: Int = 1
|
||||
var since: Int = 1
|
||||
private set
|
||||
|
||||
@RealmField("deprecatedIn")
|
||||
private var deprecatedIn: Int = 0
|
||||
var deprecatedIn: Int = 0
|
||||
private set
|
||||
|
||||
@RealmField("removedIn")
|
||||
private var removedIn: Int = 0
|
||||
var removedIn: Int = 0
|
||||
private set
|
||||
|
||||
override fun computeId() {
|
||||
this.id = Objects.hashCode(this.since, this.removedIn, this.deprecatedIn)
|
||||
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* 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.indexing.apiinfo
|
||||
|
||||
import androidx.collection.mutableIntObjectMapOf
|
||||
import jaxp.xml.namespace.QName
|
||||
import jaxp.xml.stream.XMLInputFactory
|
||||
import jaxp.xml.stream.events.Attribute
|
||||
import jaxp.xml.stream.events.EndElement
|
||||
import jaxp.xml.stream.events.StartElement
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.InputStream
|
||||
|
||||
/**
|
||||
* Parser for parsing `api-versions.xml` file from the Android SDK and building [ApiInfo] models.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ApiInfoParser {
|
||||
|
||||
private val apiInfos = mutableIntObjectMapOf<ApiInfo>()
|
||||
private val apiInfo = HashMap<String, Pair<ApiInfo, HashMap<String, ApiInfo>>>()
|
||||
|
||||
private var apiVersion: Int? = null
|
||||
private var currentClass: String? = null
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(ApiInfoParser::class.java)
|
||||
}
|
||||
|
||||
fun parse(apiVersionsXml: InputStream) {
|
||||
if (apiVersionsXml.available() <= 0) {
|
||||
log.warn("api-versions.xml InputStream is empty")
|
||||
return
|
||||
}
|
||||
|
||||
val inputFactory = XMLInputFactory.newInstance()
|
||||
val reader = inputFactory.createXMLEventReader(apiVersionsXml)
|
||||
|
||||
while (reader.hasNext()) {
|
||||
val event = reader.nextEvent()
|
||||
if (event.isStartElement) {
|
||||
consumeStartElement(event.asStartElement())
|
||||
continue
|
||||
}
|
||||
|
||||
if (event.isEndElement) {
|
||||
consumeEndElement(event.asEndElement())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [ApiInfo] for the given class.
|
||||
*/
|
||||
fun getClassInfo(className: String): ApiInfo? {
|
||||
return apiInfo[className]?.first
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns [ApiInfo] for the given class and member.
|
||||
*/
|
||||
fun getMemberInfo(className: String, memberName: String): ApiInfo? {
|
||||
return apiInfo[className]?.second?.get(memberName)
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and returns the [ApiInfo] for the given class and member. This also removes the [ApiInfo]
|
||||
* for the class if the all the members of the class have been removed.
|
||||
*/
|
||||
fun removeMemberInfo(className: String, memberName: String): ApiInfo? {
|
||||
return apiInfo[className]?.second?.remove(memberName).also {
|
||||
if (apiInfo[className]?.second?.isEmpty() == true) {
|
||||
apiInfo.remove(className)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeStartElement(event: StartElement) {
|
||||
when (event.name.localPart) {
|
||||
"api" -> apiVersion = event.getAttributeByName(QName("version")).value.toInt()
|
||||
"class" -> consumeClass(event)
|
||||
"field" -> consumeMember(event, "field")
|
||||
"method" -> consumeMember(event, "method")
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeClass(event: StartElement) {
|
||||
checkNotNull(apiVersion) {
|
||||
"<class> element must be inside <api> element"
|
||||
}
|
||||
|
||||
check(currentClass == null) {
|
||||
"<class> elements cannot be nested"
|
||||
}
|
||||
|
||||
val (name, versions) = event.parseAttrs()
|
||||
check(!apiInfo.containsKey(name)) {
|
||||
"Duplicate class entry: $name"
|
||||
}
|
||||
|
||||
val apiInfo = apiInfos.getOrPut(versions) {
|
||||
createApiInfo(versions)
|
||||
}
|
||||
|
||||
this.apiInfo[name] = apiInfo to HashMap()
|
||||
this.currentClass = name
|
||||
}
|
||||
|
||||
private fun consumeMember(event: StartElement, memberType: String) {
|
||||
val (name, versions) = event.parseAttrs()
|
||||
val currentClass = checkNotNull(currentClass) {
|
||||
"<${memberType}> element must be inside <class> element"
|
||||
}
|
||||
check(!apiInfo.containsKey(name)) {
|
||||
"Duplicate $memberType entry in class $currentClass: $name"
|
||||
}
|
||||
|
||||
val (_, members) = apiInfo[currentClass]!!
|
||||
val existing = members.put(name, createApiInfo(versions))
|
||||
check(existing == null) {
|
||||
"Duplicate $memberType entry in class $currentClass: $name"
|
||||
}
|
||||
}
|
||||
|
||||
private fun consumeEndElement(element: EndElement) {
|
||||
when (element.name.localPart) {
|
||||
"api" -> apiVersion = null
|
||||
"class" -> currentClass = null
|
||||
}
|
||||
}
|
||||
|
||||
private fun StartElement.parseAttrs(): Pair<String, Int> {
|
||||
var name: String? = null
|
||||
var since = 1
|
||||
var deprecated = 0
|
||||
var removed = 0
|
||||
|
||||
attributes.forEach { attribute ->
|
||||
attribute as Attribute
|
||||
|
||||
when (attribute.name.localPart) {
|
||||
"name" -> name = attribute.value
|
||||
"since" -> since = attribute.value.toInt()
|
||||
"deprecated" -> deprecated = attribute.value.toInt()
|
||||
"removed" -> removed = attribute.value.toInt()
|
||||
}
|
||||
}
|
||||
|
||||
checkNotNull(name) {
|
||||
"Missing name attribute"
|
||||
}
|
||||
|
||||
// Android API versions would not exceed 255, right?
|
||||
check(since in 1..255 && deprecated in 0..255 && removed in 0..255) {
|
||||
"Invalid version: $since, $deprecated, $removed"
|
||||
}
|
||||
|
||||
val version = (since shl 16) + (deprecated shl 8) + removed
|
||||
return name!! to version
|
||||
}
|
||||
|
||||
private fun createApiInfo(versions: Int): ApiInfo {
|
||||
val since = (versions shr 16) and 0x000000FF
|
||||
val deprecated = (versions shr 8) and 0x000000FF
|
||||
val removed = versions and 0x000000FF
|
||||
return ApiInfo.newInstance(since = since, deprecatedIn = deprecated, removedIn = removed)
|
||||
}
|
||||
}
|
||||
+342
@@ -0,0 +1,342 @@
|
||||
/*
|
||||
* 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.indexing.apiinfo
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.itsaky.androidide.testing.common.utils.findAndroidHome
|
||||
import jaxp.xml.stream.XMLStreamException
|
||||
import org.junit.Assert.assertThrows
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class ApiInfoParserTest {
|
||||
|
||||
val apiVersionXml by lazy {
|
||||
val platformsDir = File(findAndroidHome(), "platforms")
|
||||
val platformDir = platformsDir.listFiles()!!.asSequence().sortedByDescending {
|
||||
// android-30, android-31, android-32, android-33, etc
|
||||
it.name.substring("android-".length).toIntOrNull() ?: 0
|
||||
}.first()
|
||||
|
||||
platformDir.resolve("data/api-versions.xml")
|
||||
}
|
||||
|
||||
private lateinit var parser: ApiInfoParser
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
parser = ApiInfoParser()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test basic parse`() {
|
||||
parser.parse(apiVersionXml.inputStream().buffered())
|
||||
|
||||
val manifestInfo = parser.getClassInfo("android/Manifest")
|
||||
assertThat(manifestInfo).isNotNull()
|
||||
assertThat(manifestInfo?.since).isEqualTo(1)
|
||||
assertThat(manifestInfo?.deprecatedIn).isEqualTo(0)
|
||||
assertThat(manifestInfo?.removedIn).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleClassParsing() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="0" removed="0"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
parser.parse(inputStream)
|
||||
|
||||
val classInfo = parser.getClassInfo("com.example.ClassA")
|
||||
assertThat(classInfo).isNotNull()
|
||||
assertThat(classInfo!!.since).isEqualTo(1)
|
||||
assertThat(classInfo.deprecatedIn).isEqualTo(0)
|
||||
assertThat(classInfo.removedIn).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSingleClassWithMembersParsing() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="0" removed="0">
|
||||
<field name="fieldA" since="2" deprecated="0" removed="0"/>
|
||||
<method name="methodA" since="3" deprecated="0" removed="0"/>
|
||||
</class>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
parser.parse(inputStream)
|
||||
|
||||
val classInfo = parser.getClassInfo("com.example.ClassA")
|
||||
assertThat(classInfo).isNotNull()
|
||||
assertThat(classInfo!!.since).isEqualTo(1)
|
||||
|
||||
val fieldInfo = parser.getMemberInfo("com.example.ClassA", "fieldA")
|
||||
assertThat(fieldInfo).isNotNull()
|
||||
assertThat(fieldInfo!!.since).isEqualTo(2)
|
||||
|
||||
val methodInfo = parser.getMemberInfo("com.example.ClassA", "methodA")
|
||||
assertThat(methodInfo).isNotNull()
|
||||
assertThat(methodInfo!!.since).isEqualTo(3)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultipleClassesParsing() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="0" removed="0"/>
|
||||
<class name="com.example.ClassB" since="2" deprecated="0" removed="0"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
parser.parse(inputStream)
|
||||
|
||||
val classAInfo = parser.getClassInfo("com.example.ClassA")
|
||||
assertThat(classAInfo).isNotNull()
|
||||
assertThat(classAInfo!!.since).isEqualTo(1)
|
||||
|
||||
val classBInfo = parser.getClassInfo("com.example.ClassB")
|
||||
assertThat(classBInfo).isNotNull()
|
||||
assertThat(classBInfo!!.since).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClassWithDeprecatedAndRemovedAttributes() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="30" removed="34"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
parser.parse(inputStream)
|
||||
|
||||
val classInfo = parser.getClassInfo("com.example.ClassA")
|
||||
assertThat(classInfo).isNotNull()
|
||||
assertThat(classInfo!!.since).isEqualTo(1)
|
||||
assertThat(classInfo.deprecatedIn).isEqualTo(30)
|
||||
assertThat(classInfo.removedIn).isEqualTo(34)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClassWithInvalidAttributes() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="256" removed="0"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
try {
|
||||
parser.parse(inputStream)
|
||||
assertThat(true).isFalse() // Should not reach here
|
||||
} catch (e: IllegalStateException) {
|
||||
assertThat(e).hasMessageThat().contains("Invalid version")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMissingNameAttribute() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class since="1" deprecated="0" removed="0"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
try {
|
||||
parser.parse(inputStream)
|
||||
assertThat(true).isFalse() // Should not reach here
|
||||
} catch (e: IllegalStateException) {
|
||||
assertThat(e).hasMessageThat().contains("Missing name attribute")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDuplicateClassEntry() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="0" removed="0"/>
|
||||
<class name="com.example.ClassA" since="2" deprecated="0" removed="0"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
try {
|
||||
parser.parse(inputStream)
|
||||
assertThat(true).isFalse() // Should not reach here
|
||||
} catch (e: IllegalStateException) {
|
||||
assertThat(e).hasMessageThat().contains("Duplicate class entry")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDuplicateMemberEntry() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="0" removed="0">
|
||||
<field name="fieldA" since="2" deprecated="0" removed="0"/>
|
||||
<field name="fieldA" since="3" deprecated="0" removed="0"/>
|
||||
</class>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
try {
|
||||
parser.parse(inputStream)
|
||||
assertThat(true).isFalse() // Should not reach here
|
||||
} catch (e: IllegalStateException) {
|
||||
assertThat(e).hasMessageThat().contains("Duplicate field entry in class com.example.ClassA")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultipleApiElements() {
|
||||
val xml = """
|
||||
<api version="33">
|
||||
<class name="com.example.ClassA" since="1" deprecated="0" removed="0"/>
|
||||
</api>
|
||||
<api version="34">
|
||||
<class name="com.example.ClassB" since="2" deprecated="0" removed="0"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
assertThrows("The markup in the document following the root element must be well-formed.", XMLStreamException::class.java) {
|
||||
parser.parse(inputStream)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNestedClassElements() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.OuterClass" since="1" deprecated="0" removed="0">
|
||||
<class name="com.example.OuterClass.InnerClass" since="2" deprecated="0" removed="0"/>
|
||||
</class>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
|
||||
assertThrows("<class> elements cannot be nested", IllegalStateException::class.java) {
|
||||
parser.parse(inputStream)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBoundaryAttributeValues() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="255" removed="0"/>
|
||||
<class name="com.example.ClassB" since="1" deprecated="0" removed="255"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
parser.parse(inputStream)
|
||||
|
||||
val classAInfo = parser.getClassInfo("com.example.ClassA")
|
||||
assertThat(classAInfo).isNotNull()
|
||||
assertThat(classAInfo!!.deprecatedIn).isEqualTo(255)
|
||||
|
||||
val classBInfo = parser.getClassInfo("com.example.ClassB")
|
||||
assertThat(classBInfo).isNotNull()
|
||||
assertThat(classBInfo!!.removedIn).isEqualTo(255)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOutOfBoundaryAttributeValues() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<class name="com.example.ClassA" since="1" deprecated="256" removed="0"/>
|
||||
<class name="com.example.ClassB" since="1" deprecated="0" removed="256"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
assertThrows("Invalid version: 1, 256, 0", IllegalStateException::class.java) {
|
||||
parser.parse(inputStream)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUnexpectedElements() {
|
||||
val xml = """
|
||||
<api version="34">
|
||||
<unexpectedElement name="unexpected" since="1" deprecated="0" removed="0"/>
|
||||
<class name="com.example.ClassA" since="1" deprecated="0" removed="0"/>
|
||||
</api>
|
||||
""".trimIndent()
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
parser.parse(inputStream)
|
||||
|
||||
val classInfo = parser.getClassInfo("com.example.ClassA")
|
||||
assertThat(classInfo).isNotNull()
|
||||
assertThat(classInfo!!.since).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEmptyXmlFile() {
|
||||
val xml = ""
|
||||
val inputStream = ByteArrayInputStream(xml.toByteArray())
|
||||
parser.parse(inputStream)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLargeInputFile() {
|
||||
val xmlBuilder = StringBuilder()
|
||||
xmlBuilder.append("<api version=\"34\">")
|
||||
for (i in 1..1000) {
|
||||
xmlBuilder.append("<class name=\"com.example.Class$i\" since=\"${(i % 254) + 1}\" />")
|
||||
}
|
||||
xmlBuilder.append("</api>")
|
||||
val inputStream = ByteArrayInputStream(xmlBuilder.toString().toByteArray())
|
||||
|
||||
val parser = ApiInfoParser()
|
||||
parser.parse(inputStream)
|
||||
|
||||
for (i in 1..1000) {
|
||||
val classInfo = parser.getClassInfo("com.example.Class$i")
|
||||
assertThat(classInfo).isNotNull()
|
||||
assertThat(classInfo!!.since).isEqualTo((i % 254) + 1)
|
||||
assertThat(classInfo.deprecatedIn).isEqualTo(0)
|
||||
assertThat(classInfo.removedIn).isEqualTo(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.testing.common.utils
|
||||
|
||||
/**
|
||||
* Information about the operating system.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
object OperatingSystem {
|
||||
|
||||
/**
|
||||
* The name of the operating system.
|
||||
*/
|
||||
val OS = System.getProperty("os.name")
|
||||
|
||||
/**
|
||||
* True if the operating system is Windows.
|
||||
*/
|
||||
var IS_WINDOWS: Boolean = OS.indexOf("win") >= 0
|
||||
|
||||
/**
|
||||
* True if the operating system is Mac.
|
||||
*/
|
||||
var IS_MAC: Boolean = OS.indexOf("mac") >= 0
|
||||
|
||||
/**
|
||||
* True if the operating system is Unix.
|
||||
*/
|
||||
var IS_UNIX: Boolean = OS.indexOf("nix") >= 0 ||
|
||||
OS.indexOf("nux") >= 0 ||
|
||||
OS.indexOf("aix") > 0
|
||||
|
||||
/**
|
||||
* True if the operating system is Solaris.
|
||||
*/
|
||||
var IS_SOLARIS: Boolean = OS.indexOf("sunos") >= 0
|
||||
}
|
||||
+6
-6
@@ -15,7 +15,7 @@
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.testing.tooling
|
||||
package com.itsaky.androidide.testing.common.utils
|
||||
|
||||
fun findAndroidHome(): String {
|
||||
var androidHome = System.getenv("ANDROID_HOME")
|
||||
@@ -28,11 +28,11 @@ fun findAndroidHome(): String {
|
||||
return androidHome
|
||||
}
|
||||
|
||||
val os = System.getProperty("os.name")
|
||||
val home = System.getProperty("user.home")
|
||||
return if (os.contains("Linux")) {
|
||||
"$home/Android/Sdk"
|
||||
} else {
|
||||
"$home\\AppData\\Local\\Android\\Sdk"
|
||||
return when {
|
||||
OperatingSystem.IS_UNIX -> "$home/Android/Sdk"
|
||||
OperatingSystem.IS_MAC -> "$home/Library/Android/sdk"
|
||||
OperatingSystem.IS_WINDOWS -> "$home\\AppData\\Local\\Android\\Sdk"
|
||||
else -> throw IllegalStateException("Unsupported operating system")
|
||||
}
|
||||
}
|
||||
+7
-4
@@ -113,7 +113,7 @@ object ToolingApiTestLauncher {
|
||||
)
|
||||
|
||||
val builder = ProcessBuilder(cmdLine)
|
||||
val androidHome = findAndroidHome()
|
||||
val androidHome = com.itsaky.androidide.testing.common.utils.findAndroidHome()
|
||||
|
||||
builder.environment()["ANDROID_SDK_ROOT"] = androidHome
|
||||
builder.environment()["ANDROID_HOME"] = androidHome
|
||||
@@ -124,7 +124,8 @@ object ToolingApiTestLauncher {
|
||||
proc.onExit().whenComplete { process, error ->
|
||||
if (process != null) {
|
||||
println(
|
||||
"[ToolingApiTestLauncher] Tooling API server process finished with exit code: ${process.exitValue()}")
|
||||
"[ToolingApiTestLauncher] Tooling API server process finished with exit code: ${process.exitValue()}"
|
||||
)
|
||||
}
|
||||
if (error != null) {
|
||||
println("[ToolingApiTestLauncher] Tooling API server process error")
|
||||
@@ -134,8 +135,10 @@ object ToolingApiTestLauncher {
|
||||
|
||||
Thread(Reader(proc.errorStream, params.log)).start()
|
||||
val launcher =
|
||||
ToolingApiLauncher.newClientLauncher(params.client, proc.inputStream,
|
||||
proc.outputStream)
|
||||
ToolingApiLauncher.newClientLauncher(
|
||||
params.client, proc.inputStream,
|
||||
proc.outputStream
|
||||
)
|
||||
|
||||
launcher.startListening()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user