main

square/leakcanary

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

ReferenceMatcherTest.kt

TLDR

This file is a Test class that contains various test cases for the ReferenceMatcher class in the shark package. It tests different scenarios related to memory leaks and reference matching.

Classes

ReferenceMatcherTest

This class contains test methods for various scenarios to test the functionality of the ReferenceMatcher class. The test cases include checking for memory leaks, excluding certain paths, and matching specific reference patterns.

Methods

No methods are defined in this file.

package shark

import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import shark.GcRoot.JniGlobal
import shark.ReferencePattern.InstanceFieldPattern
import shark.ReferencePattern.JavaLocalPattern
import shark.ReferencePattern.NativeGlobalVariablePattern
import shark.ReferencePattern.StaticFieldPattern
import shark.ValueHolder.ReferenceHolder
import java.io.File
import java.lang.ref.WeakReference

class ReferenceMatcherTest {

  @get:Rule
  var testFolder = TemporaryFolder()
  private lateinit var hprofFile: File

  @Before
  fun setUp() {
    hprofFile = testFolder.newFile("temp.hprof")
  }

  @Test fun shortestPathExcluded() {
    hprofFile.writeTwoPathsToInstance()

    val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>(
      referenceMatchers = listOf(
        LibraryLeakReferenceMatcher(StaticFieldPattern("GcRoot", "shortestPath"))
      )
    )

    val leakTrace = analysis.applicationLeaks[0].leakTraces.first()
    assertThat(leakTrace.referencePath).hasSize(2)
    assertThat(leakTrace.referencePath[0].originObject.className).isEqualTo("GcRoot")
    assertThat(leakTrace.referencePath[0].referenceName).isEqualTo("longestPath")
    assertThat(leakTrace.referencePath[1].originObject.className).isEqualTo("HasLeaking")
    assertThat(leakTrace.referencePath[1].referenceName).isEqualTo("leaking")
    assertThat(leakTrace.leakingObject.className).isEqualTo("Leaking")
  }

  @Test fun allPathsExcluded_ShortestWins() {
    hprofFile.writeTwoPathsToInstance()

    val expectedMatcher = LibraryLeakReferenceMatcher(StaticFieldPattern("GcRoot", "shortestPath"))
    val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>(
      referenceMatchers = listOf(
        expectedMatcher,
        LibraryLeakReferenceMatcher(InstanceFieldPattern("HasLeaking", "leaking"))
      )
    )

    val leak = analysis.libraryLeaks[0]
    assertThat(leak.pattern).isEqualTo(expectedMatcher.pattern)
    val leakTrace = leak.leakTraces.first()
    assertThat(leakTrace.referencePath).hasSize(1)
    assertThat(leakTrace.referencePath[0].originObject.className).isEqualTo("GcRoot")
    assertThat(leakTrace.referencePath[0].referenceName).isEqualTo("shortestPath")
    assertThat(leakTrace.leakingObject.className).isEqualTo("Leaking")
  }

  @Test fun noPathToInstanceNeverReachable() {
    hprofFile.writeTwoPathsToInstance()

    val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>(
      referenceMatchers = listOf(
        IgnoredReferenceMatcher(StaticFieldPattern("GcRoot", "shortestPath")),
        IgnoredReferenceMatcher(InstanceFieldPattern("HasLeaking", "leaking"))
      )
    )
    assertThat(analysis.libraryLeaks).isEmpty()
  }

  @Test fun excludedThread() {
    hprofFile.writeJavaLocalLeak(threadClass = "MyThread", threadName = "kroutine")

    val matcher = LibraryLeakReferenceMatcher(JavaLocalPattern("kroutine"))
    val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>(
      referenceMatchers = listOf(matcher)
    )

    val leak = analysis.libraryLeaks[0]
    assertThat(leak.pattern).isEqualTo(matcher.pattern)
  }

  @Test fun overrideSuperclassExclusion() {
    hprofFile.dump {
      "GcRoot" clazz {
        staticField["ref"] =
          keyedWeakReference(referentInstanceId = "Leaking" instance {})
      }
    }

    val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>(
      referenceMatchers = listOf(
        LibraryLeakReferenceMatcher(
          pattern = InstanceFieldPattern(WeakReference::class.java.name, "referent")
        ),
        IgnoredReferenceMatcher(
          pattern = InstanceFieldPattern("leakcanary.KeyedWeakReference", "referent")
        )
      )
    )
    assertThat(analysis.libraryLeaks).isEmpty()
  }

  @Test fun nativeGlobalVariableLibraryLeak() {
    hprofFile.dump {
      gcRoot(JniGlobal(id = "Leaking".watchedInstance {}.value, jniGlobalRefId = 42))
    }

    val matcher = LibraryLeakReferenceMatcher(NativeGlobalVariablePattern("Leaking"))
    val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>(
      referenceMatchers = listOf(matcher)
    )
    val leak = analysis.libraryLeaks[0]
    assertThat(leak.pattern).isEqualTo(matcher.pattern)
  }

  @Test fun nativeGlobalVariableShortestPathExcluded() {
    hprofFile.dump {
      val leaking = instance(clazz("Leaking"))
      keyedWeakReference(leaking)
      val hasLeaking = instance(
        clazz("HasLeaking", fields = listOf("leaking" to ReferenceHolder::class)),
        fields = listOf(leaking)
      )
      clazz("GcRoot", staticFields = listOf("longestPath" to hasLeaking))
      gcRoot(JniGlobal(id = leaking.value, jniGlobalRefId = 42))
    }

    val matcher = LibraryLeakReferenceMatcher(NativeGlobalVariablePattern("Leaking"))
    val analysis = hprofFile.checkForLeaks<HeapAnalysisSuccess>(
      referenceMatchers = listOf(matcher)
    )
    val leakTrace = analysis.applicationLeaks[0].leakTraces.first()
    assertThat(leakTrace.referencePath).hasSize(2)
    assertThat(leakTrace.referencePath[0].originObject.className).isEqualTo("GcRoot")
    assertThat(leakTrace.referencePath[0].referenceName).isEqualTo("longestPath")
  }
}