LeakActivityTest.kt
TLDR
This file contains the LeakActivityTest
class, which is responsible for testing the behavior of the LeakActivity
class in the LeakCanary library. It includes several test methods that simulate different scenarios and assertions to verify the expected behavior of the activity.
Methods
noLeakOnHome
This method tests the behavior of the LeakActivity
when there are no leaks on the home screen. It launches the activity and checks that the text "0 Distinct Leaks" is displayed.
oneLeakOnHome
This method tests the behavior of the LeakActivity
when there is one leak on the home screen. It inserts a heap dump with a leaking instance, launches the activity, and checks that the text "1 Distinct Leak" is displayed.
seeLeakOnLeakScreen
This method tests the behavior of the LeakActivity
when viewing a leak on the leak screen. It inserts a heap dump with a leaking instance, launches the activity, performs a click action on the leak item, and checks that the leak trace is displayed.
leakWithEmptyReferencePath
This method tests the behavior of the LeakActivity
when there is a leak with an empty reference path. It inserts a heap dump with a leaking instance and a JniGlobal as a GC root, launches the activity, performs a click action on the leak item, and checks that the leak trace is displayed.
writeHeapDump(block: HprofWriterHelper.() -> Unit): File
This method writes a heap dump file using the specified block of code. It creates a temporary file, creates an HprofWriterHelper
instance, and calls the block to build the heap dump content. It returns the created file.
insertHeapDump(block: HprofWriterHelper.() -> Unit)
This method inserts a heap dump into the database. It calls the writeHeapDump
method to create a heap dump file and uses a HeapAnalyzer
to analyze the dump and get the result. Then it inserts the result into the database using the ScopedLeaksDb
class.
withItem(T, filterDescription: String?, filter: (T) -> Boolean): Matcher<T>
This inline function creates a TypeSafeMatcher
that matches an item of type T
based on the provided filter function. It allows specifying a filter description for better test failure messages.
Classes
LeakActivityTest
This class tests the behavior of the LeakActivity
class in the LeakCanary library. It includes several test methods that simulate different scenarios and assertions to verify the expected behavior of the activity. The class also contains helper methods for writing and inserting heap dumps into the database.
END
package leakcanary
import androidx.test.espresso.Espresso.onData
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.rule.ActivityTestRule
import com.squareup.leakcanary.core.R
import java.io.File
import leakcanary.internal.activity.LeakActivity
import leakcanary.internal.activity.db.HeapAnalysisTable
import leakcanary.internal.activity.db.LeakTable.AllLeaksProjection
import leakcanary.internal.activity.db.ScopedLeaksDb
import org.hamcrest.Description
import org.hamcrest.Matcher
import org.hamcrest.TypeSafeMatcher
import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.rules.TemporaryFolder
import shark.GcRoot.JniGlobal
import shark.HeapAnalyzer
import shark.HprofWriterHelper
import shark.LeakTraceObject
import shark.OnAnalysisProgressListener
import shark.ValueHolder.IntHolder
import shark.dump
internal class LeakActivityTest {
private val activityTestRule = ActivityTestRule(LeakActivity::class.java, false, false)
@get:Rule
var testFolder = TemporaryFolder()
@get:Rule
var rules: RuleChain = RuleChain.outerRule(DatabaseRule())
.around(activityTestRule)
@Test
fun noLeakOnHome() {
activityTestRule.launchActivity(null)
onView(withText("0 Distinct Leaks")).check(matches(isDisplayed()))
}
@Test
fun oneLeakOnHome() {
insertHeapDump {
"Holder" clazz {
staticField["leak"] = "com.example.Leaking" watchedInstance {}
}
}
activityTestRule.launchActivity(null)
onView(withText("1 Distinct Leak")).check(matches(isDisplayed()))
}
@Test
fun seeLeakOnLeakScreen() {
insertHeapDump {
"Holder" clazz {
staticField["leak"] = "com.example.Leaking" watchedInstance {}
}
}
activityTestRule.launchActivity(null)
onData(withItem<AllLeaksProjection> { it.shortDescription == "Holder.leak" })
.perform(click())
onData(withItem<LeakTraceObject> { it.className == "com.example.Leaking" })
.inAdapterView(withId(R.id.leak_canary_list))
.check(matches(isDisplayed()))
}
@Test
fun leakWithEmptyReferencePath() {
insertHeapDump {
val leakingInstance = "com.example.Leaking" watchedInstance {}
gcRoot(JniGlobal(id = leakingInstance.value, jniGlobalRefId = 42))
}
activityTestRule.launchActivity(null)
onData(withItem<AllLeaksProjection> { it.shortDescription == "com.example.Leaking" })
.perform(click())
onData(withItem<LeakTraceObject> { it.className == "com.example.Leaking" })
.inAdapterView(withId(R.id.leak_canary_list))
.check(matches(isDisplayed()))
}
private fun writeHeapDump(block: HprofWriterHelper.() -> Unit): File {
val hprofFile = testFolder.newFile("temp.hprof")
hprofFile.dump {
"android.os.Build" clazz {
staticField["MANUFACTURER"] = string("Samsing")
staticField["ID"] = string("M4-rc20")
}
"android.os.Build\$VERSION" clazz {
staticField["SDK_INT"] = IntHolder(47)
}
block()
}
return hprofFile
}
private fun insertHeapDump(block: HprofWriterHelper.() -> Unit) {
val hprofFile = writeHeapDump(block)
val heapAnalyzer = HeapAnalyzer(OnAnalysisProgressListener.NO_OP)
val result = heapAnalyzer.analyze(
heapDumpFile = hprofFile,
leakingObjectFinder = LeakCanary.config.leakingObjectFinder,
referenceMatchers = LeakCanary.config.referenceMatchers,
computeRetainedHeapSize = LeakCanary.config.computeRetainedHeapSize,
objectInspectors = LeakCanary.config.objectInspectors,
metadataExtractor = LeakCanary.config.metadataExtractor,
proguardMapping = null
)
val instrumentation = InstrumentationRegistry.getInstrumentation()
val context = instrumentation.targetContext
ScopedLeaksDb.writableDatabase(context) { db ->
HeapAnalysisTable.insert(db, result)
}
}
inline fun <reified T : Any> withItem(
filterDescription: String? = null,
crossinline filter: (T) -> Boolean
): Matcher<T> {
return object : TypeSafeMatcher<T>(T::class.java) {
override fun describeTo(description: Description) {
if (filterDescription != null) {
description.appendText("is $filterDescription")
}
}
override fun matchesSafely(item: T): Boolean {
return filter(item)
}
}
}
}
fun tryAndRestoreConfig(block: () -> Unit) {
val original = LeakCanary.config
try {
block()
} finally {
LeakCanary.config = original
}
}