AndroidReferenceReadersHprofTest.kt
TLDR
The AndroidReferenceReadersHprofTest.kt
file contains test cases for checking references and leaks in a heap dump file (hprof
file). It also includes helper functions for loading files and checking for leaks.
Classes
AndroidReferenceReadersHprofTest
This class contains test methods for checking references and leaks in a heap dump file. The file is loaded and analyzed using the checkForLeaks
function from the Shark library. Different assertions are made on the references found in the leak traces.
Helper functions
-
File.checkForFakeArraySetLeak()
: This function is an extension function on theFile
class. It checks for a specific fake leak in the heap graph using the Shark library. It creates a customLeakingObjectFinder
implementation and uses it in thecheckForLeaks
function. -
String.classpathFile()
: This function is an extension function on theString
class. It converts a classpath string to aFile
object.
package shark.internal
import java.io.File
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import shark.HeapAnalysisSuccess
import shark.HeapGraph
import shark.IgnoredReferenceMatcher
import shark.LeakTraceReference.ReferenceType.ARRAY_ENTRY
import shark.LeakingObjectFinder
import shark.ReferencePattern.StaticFieldPattern
import shark.checkForLeaks
import shark.defaultReferenceMatchers
class AndroidReferenceReadersHprofTest {
@Test fun `safe iterable map traversed as dictionary`() {
val hprofFile = "safe_iterable_map.hprof".classpathFile()
val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>()
val leakTrace = analysis.applicationLeaks.single().leakTraces.single()
val mapReference =
leakTrace.referencePath.single { it.owningClassSimpleName == "FastSafeIterableMap" }
assertThat(mapReference.referenceName).isEqualTo("key()")
assertThat(mapReference.referenceType).isEqualTo(ARRAY_ENTRY)
}
@Test fun `API 25 HashMap$HashMapEntry supported`() {
val hprofFile = "hashmap_api_25.hprof".classpathFile()
val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>()
val leakTrace = analysis.applicationLeaks.single().leakTraces.single()
val mapReference =
leakTrace.referencePath.single { it.owningClassSimpleName == "HashMap" }
assertThat(mapReference.referenceName).isEqualTo("\"leaking\"")
assertThat(mapReference.referenceType).isEqualTo(ARRAY_ENTRY)
}
@Test fun `ArraySet traversed as set`() {
// This hprof happens to have an ArraySet in it.
val hprofFile = "safe_iterable_map.hprof".classpathFile()
val analysis = hprofFile.checkForFakeArraySetLeak()
val leakTrace = analysis.applicationLeaks.single().leakTraces.single()
println(leakTrace)
val mapReference =
leakTrace.referencePath.single { it.owningClassSimpleName == "ArraySet" }
assertThat(mapReference.referenceName).isEqualTo("element()")
assertThat(mapReference.referenceType).isEqualTo(ARRAY_ENTRY)
}
}
fun File.checkForFakeArraySetLeak(): HeapAnalysisSuccess {
val instanceHeldByArraySet =
"android.view.accessibility.AccessibilityNodeInfo\$AccessibilityAction"
class ArraySetFakeLeakingObjectFinder : LeakingObjectFinder {
override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> {
val arraySetInstances = graph.findClassByName("android.util.ArraySet")!!
.instances
.map { arraySetInstance ->
arraySetInstance to arraySetInstance["android.util.ArraySet", "mArray"]!!
.valueAsObjectArray!!
.readElements()
.filter {
it.asObject?.asInstance?.instanceClass?.name == instanceHeldByArraySet
}
.toList()
}
val firstElementReferencedByArraySet = arraySetInstances.first { (_, elements) ->
elements.isNotEmpty()
}.second.first()
return setOf(firstElementReferencedByArraySet.asObjectId!!)
}
}
return checkForLeaks(
referenceMatchers = defaultReferenceMatchers + IgnoredReferenceMatcher(
StaticFieldPattern(
instanceHeldByArraySet,
"ACTION_FOCUS"
)
),
leakingObjectFinder = ArraySetFakeLeakingObjectFinder()
)
}
fun String.classpathFile(): File {
val classLoader = Thread.currentThread()
.contextClassLoader
val url = classLoader.getResource(this)!!
return File(url.path)
}