Skip to content

Commit

Permalink
Merge pull request #33 from Banno/dexlib2
Browse files Browse the repository at this point in the history
Update to dexlib2
  • Loading branch information
joshschriever authored May 5, 2020
2 parents ff033d1 + 137b885 commit 59b1905
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 56 deletions.
2 changes: 1 addition & 1 deletion gordon-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
92 changes: 37 additions & 55 deletions gordon-plugin/src/main/kotlin/com/banno/gordon/TestSuiteLoader.kt
Original file line number Diff line number Diff line change
@@ -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<List<TestCase>> = 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" }

0 comments on commit 59b1905

Please sign in to comment.