diff --git a/gordon-plugin/build.gradle.kts b/gordon-plugin/build.gradle.kts index fa3854a..46294b8 100644 --- a/gordon-plugin/build.gradle.kts +++ b/gordon-plugin/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { implementation("com.android.tools.build:gradle:$androidGradlePluginVersion") implementation("com.github.vidstige:jadb:v1.1.0") - implementation("org.smali:dexlib:1.4.2") + implementation("org.smali:dexlib2:2.4.0") implementation("com.shazam:axmlparser:1.0") implementation("io.arrow-kt:arrow-core-data:0.10.4") diff --git a/gordon-plugin/src/main/kotlin/com/banno/gordon/TestSuiteLoader.kt b/gordon-plugin/src/main/kotlin/com/banno/gordon/TestSuiteLoader.kt index 0dab7aa..eadc0d7 100644 --- a/gordon-plugin/src/main/kotlin/com/banno/gordon/TestSuiteLoader.kt +++ b/gordon-plugin/src/main/kotlin/com/banno/gordon/TestSuiteLoader.kt @@ -1,67 +1,49 @@ package com.banno.gordon import arrow.fx.IO -import org.jf.dexlib.AnnotationDirectoryItem -import org.jf.dexlib.AnnotationItem -import org.jf.dexlib.ClassDefItem -import org.jf.dexlib.DexFile -import org.jf.dexlib.Util.AccessFlags +import org.jf.dexlib2.AccessFlags +import org.jf.dexlib2.DexFileFactory +import org.jf.dexlib2.iface.Annotatable +import org.jf.dexlib2.iface.BasicAnnotation +import org.jf.dexlib2.iface.reference.TypeReference import java.io.File -import java.util.zip.ZipFile internal fun loadTestSuite(instrumentationApk: File): IO> = IO { - ZipFile(instrumentationApk).use { zip -> - zip.entries() - .toList() - .filter { it.name.startsWith("classes") && it.name.endsWith(".dex") } - .map { zipEntry -> - File.createTempFile("dex", zipEntry.name).also { - it.deleteOnExit() - it.outputStream().use { output -> - zip.getInputStream(zipEntry).use { input -> input.copyTo(output) } - } + DexFileFactory + .loadDexContainer(instrumentationApk, null) + .run { dexEntryNames.map { getEntry(it)!!.dexFile } } + .flatMap { it.classes } + .filter { + val isKotlinInterfaceDefaultImplementation = it.name.endsWith("\$DefaultImpls") + val isInterface = (it.accessFlags and AccessFlags.INTERFACE.value != 0) + val isAbstract = (it.accessFlags and AccessFlags.ABSTRACT.value != 0) + + !isInterface && + !isAbstract && + !isKotlinInterfaceDefaultImplementation + } + .flatMap { classDef -> + classDef.methods + .mapNotNull { method -> + if (method.isTestMethod) { + TestCase( + fullyQualifiedClassName = classDef.name, + methodName = method.name, + isIgnored = method.isIgnored || classDef.isIgnored + ) + } else null } - } - .map(::DexFile) - .flatMap { it.ClassDefsSection.items } - .filter { - val isKotlinInterfaceDefaultImplementation = it.className.endsWith("\$DefaultImpls") - val isInterface = (it.accessFlags and AccessFlags.INTERFACE.value != 0) - val isAbstract = (it.accessFlags and AccessFlags.ABSTRACT.value != 0) - - !isInterface && - !isAbstract && - !isKotlinInterfaceDefaultImplementation - } - .flatMap { classDefItem -> - (classDefItem.annotations?.methodAnnotations ?: emptyList()) - .mapNotNull { method -> - if (method.isTestMethod) { - TestCase( - fullyQualifiedClassName = classDefItem.className, - methodName = method.method.methodName.stringValue, - isIgnored = method.isIgnored || classDefItem.isIgnored - ) - } else null - } - } - } + } } -private val ClassDefItem.className - get() = classType.typeDescriptor.drop(1).dropLast(1).replace('/', '.') - -private val ClassDefItem.isIgnored - get() = annotations?.classAnnotations?.annotations?.any { it.typeDescriptor == IGNORE_ANNOTATION } ?: false - -private val AnnotationDirectoryItem.MethodAnnotation.isIgnored - get() = annotationSet.annotations?.any { it.typeDescriptor == IGNORE_ANNOTATION } ?: false +private val TypeReference.name + get() = type.drop(1).dropLast(1).replace('/', '.') -private val AnnotationDirectoryItem.MethodAnnotation.isTestMethod - get() = annotationSet.annotations?.any { it.typeDescriptor == TEST_ANNOTATION } ?: false +private val BasicAnnotation.name + get() = type.drop(1).dropLast(1).replace('/', '.') -private val AnnotationItem.typeDescriptor - get() = encodedAnnotation.annotationType.typeDescriptor +private val Annotatable.isIgnored + get() = annotations.any { it.name == "org.junit.Ignore" } -private const val IGNORE_ANNOTATION = "Lorg/junit/Ignore;" -private const val TEST_ANNOTATION = "Lorg/junit/Test;" +private val Annotatable.isTestMethod + get() = annotations.any { it.name == "org.junit.Test" }