main

square/leakcanary

Last updated at: 29/12/2023 09:37

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
  }
}