main

square/leakcanary

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

LeakActivityTest.kt

TLDR

This file is a test file for the LeakCanary library in the Demo Projects project. It contains a test class called LeakActivityTest that tests the functionality of the LeakActivity class. The test imports a heap dump file, launches the LeakActivity, and performs assertions on the displayed views.

Methods

importHeapDumpFile

This method tests the importing of a heap dump file. It creates a CountDownLatch and configures the LeakCanary event listeners to count down the latch when a HeapAnalysisDone event is received. It then creates a heap dump file using the writeHeapDump method, creates an Intent to view the dump file, launches the LeakActivity with the intent, and waits for the latch to count down. Finally, it performs assertions on the displayed views.

writeHeapDump

This private method writes a heap dump file. It uses the dumpToBytes function to create byte data representing the heap dump, and writes the data to a temporary file in the testFolder.

tryAndRestoreConfig

This private method wraps a block of code and restores the original LeakCanary configuration after executing the block.

package leakcanary

import android.app.Activity
import android.content.Intent
import android.net.Uri
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.withText
import androidx.test.rule.ActivityTestRule
import java.io.File
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit.SECONDS
import leakcanary.EventListener.Event.HeapAnalysisDone
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import shark.HprofWriterHelper
import shark.ValueHolder.IntHolder
import shark.dumpToBytes

internal class LeakActivityTest {

  @get:Rule
  var testFolder = TemporaryFolder()

  @Suppress("UNCHECKED_CAST")
  // This class is internal but ActivityTestRule requires being passed in the real class.
  private val leakActivityClass = Class.forName("leakcanary.internal.activity.LeakActivity")
    as Class<Activity>

  @get:Rule
  val activityTestRule = object : ActivityTestRule<Activity>(leakActivityClass, false, false) {
    override fun getActivityIntent(): Intent {
      return LeakCanary.newLeakDisplayActivityIntent()
    }
  }

  @Test
  fun importHeapDumpFile() = tryAndRestoreConfig {
    val latch = CountDownLatch(1)
    LeakCanary.config = LeakCanary.config.run {
      copy(eventListeners = eventListeners + EventListener { event ->
        if (event is HeapAnalysisDone<*>) {
          latch.countDown()
        }
      })
    }
    val hprof = writeHeapDump {
      "Holder" clazz {
        staticField["leak"] = "com.example.Leaking" watchedInstance {}
      }
    }
    val intent = Intent(Intent.ACTION_VIEW, Uri.fromFile(hprof))
    activityTestRule.launchActivity(intent)
    require(latch.await(5, SECONDS)) {
      "Heap analysis not done within 5 seconds of starting import"
    }
    onView(withText("1 Heap Dump")).check(matches(isDisplayed()))
    onView(withText("1 Distinct Leak")).perform(click())
    onView(withText("Holder.leak")).check(matches(isDisplayed()))
  }

  private fun writeHeapDump(block: HprofWriterHelper.() -> Unit): File {
    val hprofBytes = dumpToBytes {
      "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 testFolder.newFile("temp.hprof").apply {
      writeBytes(hprofBytes)
      require(exists()) {
        "$this does not exist"
      }
      require(length().toInt() == hprofBytes.size) {
        "$this has size ${length()} instead of expected ${hprofBytes.size}"
      }
    }
  }

  private fun tryAndRestoreConfig(block: () -> Unit) {
    val original = LeakCanary.config
    try {
      block()
    } finally {
      LeakCanary.config = original
    }
  }

}