com.linkedin.dex.parser.JUnit3Extensions.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of parser Show documentation
Show all versions of parser Show documentation
Find all test methods in an Android instrumentation APK
/**
* Copyright (c) LinkedIn Corporation. All rights reserved. Licensed under the BSD-2 Clause license.
* See LICENSE in the project root for license information.
*/
package com.linkedin.dex.parser
import com.linkedin.dex.spec.ClassDataItem
import com.linkedin.dex.spec.ClassDefItem
import com.linkedin.dex.spec.DexFile
import com.linkedin.dex.spec.MethodIdItem
/**
* All of the classes that extend JUnit3's TestCase class and are included in the Android SDK.
* This set is needed because tests inside the apk we're searching may extend any of these and since
* these are part of the Android SDK, they won't be included in the apk we're searching (so they're root nodes)
*/
private val defaultDescriptors = setOf(
"Ljunit/framework/TestCase;",
"Landroid/test/ActivityInstrumentationTestCase;",
"Landroid/test/ActivityInstrumentationTestCase2;",
"Landroid/test/ActivityTestCase;",
"Landroid/test/ActivityUnitTestCase;",
"Landroid/test/AndroidTestCase;",
"Landroid/test/ApplicationTestCase;",
"Landroid/test/FailedToCreateTests;",
"Landroid/test/InstrumentationTestCase;",
"Landroid/test/LoaderTestCase;",
"Landroid/test/ProviderTestCase;",
"Landroid/test/ProviderTestCase2;",
"Landroid/test/ServiceTestCase;",
"Landroid/test/SingleLaunchActivityTestCase;",
"Landroid/test/SyncBaseInstrumentation;"
)
/**
* Recursively search through the list of DexFiles to find all JUnit3 tests
*
* This function is O(n!), but in practice this is okay because test apks will have a very small number of DexFiles
*/
fun findJUnit3Tests(dexFiles: List): Set {
return findJUnit3Tests(dexFiles, mutableSetOf(), defaultDescriptors.toMutableSet()).first
}
private fun findJUnit3Tests(dexFiles: List, testNames: MutableSet,
descriptors: MutableSet): Pair, MutableSet> {
// base case
if (dexFiles.isEmpty()) {
return Pair(testNames, descriptors)
}
// look through each dex file and find the test names and classes that extend TestCase
// pass the class list through to the next file in case we find something that extends TestCase
// in one dex file, and there's something that extends THAT in a later dex file
dexFiles.forEach { dexFile ->
val newTestNames = dexFile.findJUnit3Tests(descriptors)
testNames.addAll(newTestNames)
}
// chop off the last dex file, we've found everything in it
// recursively look through all the other dex files again in case there
// are more tests found using the data we found in the last dex file
return findJUnit3Tests(dexFiles.subList(0, dexFiles.lastIndex), testNames, descriptors)
}
private fun DexFile.findJUnit3Tests(descriptors: MutableSet): List {
val testClasses = findClassesWithSuperClass(descriptors)
return createTestMethods(testClasses, { classDef, _ -> findMethodIds(classDef) })
.filter { it.testName.contains("#test") }
}
fun DexFile.findMethodIds(classDefItem: ClassDefItem): MutableList {
return findMethodIdxs(classDefItem).map { this.methodIds[it] }.toMutableList()
}
// From the docs:
// The classes must be ordered such that a given class's superclass and
// implemented interfaces appear in the list earlier than the referring class
private fun DexFile.findClassesWithSuperClass(targetDescriptors: MutableSet): List {
val matchingClasses: MutableList = mutableListOf()
classDefs.forEach { classDefItem ->
if (hasDirectSuperClass(classDefItem, targetDescriptors)) {
matchingClasses.add(classDefItem)
targetDescriptors.add(ParseUtils.parseDescriptor(byteBuffer, typeIds[classDefItem.classIdx], stringIds))
}
}
return matchingClasses
}
private fun DexFile.hasDirectSuperClass(classDefItem: ClassDefItem, targetDescriptors: Set): Boolean {
if (classDefItem.superclassIdx == DexFile.NO_INDEX) {
return false
}
val superType = typeIds[classDefItem.superclassIdx]
val superDescriptor = ParseUtils.parseDescriptor(byteBuffer, superType, stringIds)
return targetDescriptors.contains(superDescriptor)
}