main

square/leakcanary

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

HprofWriterTest.kt

TLDR

This file contains a test class HprofWriterTest with several test methods that write and read different types of records in an Hprof format.

Methods

writeAndReadStringRecord

This method tests the functionality of writing and reading a string record in the Hprof format. It creates a string record with a given ID and string value, converts it to bytes, reads the bytes, and asserts that the read result matches the original record.

writeAndReadClassRecord

This method tests the functionality of writing and reading a class record in the Hprof format. It creates a class name record, a load class record, and a class dump record, converts them to bytes, opens a heap graph from the bytes, and asserts that the loaded class name is not null.

writeAndReadStaticField

This method tests the functionality of writing and reading a static field in the Hprof format. It creates string records for class name and field names, a load class record, a class dump record with static fields, converts them to bytes, opens a heap graph from the bytes, and asserts that the static fields are read correctly.

writeAndReadHprof

This method tests the functionality of writing and reading a complete Hprof file. It creates a list of records, converts them to bytes, reads the bytes, and asserts that the read records match the original records. It also opens a heap graph from the bytes and asserts that a specific field value in the graph matches the expected value.

Classes

HprofWriterTest

This class contains several test methods that test the functionality of writing and reading different types of records in the Hprof format. It also contains a helper method createRecords that creates a list of records for testing.

package shark

import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import shark.HprofHeapGraph.Companion.openHeapGraph
import shark.HprofRecord.HeapDumpEndRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
import shark.HprofRecord.LoadClassRecord
import shark.HprofRecord.StringRecord
import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader
import shark.ValueHolder.BooleanHolder
import shark.ValueHolder.IntHolder
import shark.ValueHolder.ReferenceHolder

class HprofWriterTest {

  private var lastId = 0L
  private val id: Long
    get() = ++lastId

  @Test
  fun writeAndReadStringRecord() {
    val record = StringRecord(id, MAGIC_WAND_CLASS_NAME)
    val bytes = listOf(record).asHprofBytes()

    val readRecords = bytes.readAllRecords()

    assertThat(readRecords).hasSize(1)
    assertThat(readRecords[0]).isInstanceOf(StringRecord::class.java)
    assertThat((readRecords[0] as StringRecord).id).isEqualTo(record.id)
    assertThat((readRecords[0] as StringRecord).string).isEqualTo(record.string)
  }

  @Test
  fun writeAndReadClassRecord() {
    val className = StringRecord(id, MAGIC_WAND_CLASS_NAME)
    val loadClassRecord = LoadClassRecord(1, id, 1, className.id)
    val classDump = ClassDumpRecord(
      id = loadClassRecord.id,
      stackTraceSerialNumber = 1,
      superclassId = 0,
      classLoaderId = 0,
      signersId = 0,
      protectionDomainId = 0,
      instanceSize = 0,
      staticFields = emptyList(),
      fields = emptyList()
    )

    val bytes = listOf(className, loadClassRecord, classDump).asHprofBytes()
    bytes.openHeapGraph().use { graph: HeapGraph ->
      assertThat(graph.findClassByName(className.string)).isNotNull
    }
  }

  @Test
  fun writeAndReadStaticField() {
    val className = StringRecord(id, MAGIC_WAND_CLASS_NAME)
    val field1Name = StringRecord(id, "field1")
    val field2Name = StringRecord(id, "field2")
    val loadClassRecord = LoadClassRecord(1, id, 1, className.id)
    val classDump = ClassDumpRecord(
      id = loadClassRecord.id,
      stackTraceSerialNumber = 1,
      superclassId = 0,
      classLoaderId = 0,
      signersId = 0,
      protectionDomainId = 0,
      instanceSize = 0,
      staticFields = listOf(
        StaticFieldRecord(field1Name.id, PrimitiveType.BOOLEAN.hprofType, BooleanHolder(true)),
        StaticFieldRecord(field2Name.id, PrimitiveType.INT.hprofType, IntHolder(42))
      ),
      fields = emptyList()
    )
    val bytes = listOf(className, field1Name, field2Name, loadClassRecord, classDump)
      .asHprofBytes()
    bytes.openHeapGraph().use { graph: HeapGraph ->
      val heapClass = graph.findClassByName(className.string)!!
      val staticFields = heapClass.readStaticFields().toList()
      assertThat(staticFields).hasSize(2)
      assertThat(staticFields[0].name).isEqualTo(field1Name.string)
      assertThat(staticFields[0].value.asBoolean).isEqualTo(true)
      assertThat(staticFields[1].name).isEqualTo(field2Name.string)
      assertThat(staticFields[1].value.asInt).isEqualTo(42)
    }
  }

  @Test
  fun writeAndReadHprof() {
    val records = createRecords()

    val bytes = records.asHprofBytes()

    val readRecords = bytes.readAllRecords()
    assertThat(readRecords).hasSameSizeAs(records + HeapDumpEndRecord)

    bytes.openHeapGraph().use { graph: HeapGraph ->
      val treasureChestClass = graph.findClassByName(
        TREASURE_CHEST_CLASS_NAME
      )!!
      val baguetteInstance =
        treasureChestClass[CONTENT_FIELD_NAME]!!.value.asObject!!.asInstance!!

      assertThat(
        baguetteInstance[BAGUETTE_CLASS_NAME, ANSWER_FIELD_NAME]!!.value.asInt!!
      ).isEqualTo(42)
    }
  }

  private fun createRecords(): List<HprofRecord> {
    val magicWandClassName = StringRecord(id, MAGIC_WAND_CLASS_NAME)
    val baguetteClassName = StringRecord(id, BAGUETTE_CLASS_NAME)
    val answerFieldName = StringRecord(id, ANSWER_FIELD_NAME)
    val treasureChestClassName = StringRecord(
      id,
      TREASURE_CHEST_CLASS_NAME
    )
    val contentFieldName = StringRecord(id, CONTENT_FIELD_NAME)
    val loadMagicWandClass = LoadClassRecord(1, id, 1, magicWandClassName.id)
    val loadBaguetteClass = LoadClassRecord(1, id, 1, baguetteClassName.id)
    val loadTreasureChestClass = LoadClassRecord(1, id, 1, treasureChestClassName.id)
    val magicWandClassDump = ClassDumpRecord(
      id = loadMagicWandClass.id,
      stackTraceSerialNumber = 1,
      superclassId = 0,
      classLoaderId = 0,
      signersId = 0,
      protectionDomainId = 0,
      instanceSize = 0,
      staticFields = emptyList(),
      fields = emptyList()
    )
    val baguetteClassDump = ClassDumpRecord(
      id = loadBaguetteClass.id,
      stackTraceSerialNumber = 1,
      superclassId = loadMagicWandClass.id,
      classLoaderId = 0,
      signersId = 0,
      protectionDomainId = 0,
      instanceSize = 0,
      staticFields = emptyList(),
      fields = listOf(FieldRecord(answerFieldName.id, PrimitiveType.INT.hprofType))
    )

    val baguetteInstanceDump = InstanceDumpRecord(
      id = id,
      stackTraceSerialNumber = 1,
      classId = loadBaguetteClass.id,
      fieldValues = byteArrayOf(0x0, 0x0, 0x0, 0x2a)
    )

    val treasureChestClassDump = ClassDumpRecord(
      id = loadTreasureChestClass.id,
      stackTraceSerialNumber = 1,
      superclassId = 0,
      classLoaderId = 0,
      signersId = 0,
      protectionDomainId = 0,
      instanceSize = 0,
      staticFields = listOf(
        StaticFieldRecord(
          contentFieldName.id, PrimitiveType.REFERENCE_HPROF_TYPE,
          ReferenceHolder(baguetteInstanceDump.id)
        )
      ),
      fields = emptyList()
    )

    return listOf(
      magicWandClassName, baguetteClassName, answerFieldName, treasureChestClassName,
      contentFieldName, loadMagicWandClass,
      loadBaguetteClass, loadTreasureChestClass,
      magicWandClassDump, baguetteClassDump, baguetteInstanceDump, treasureChestClassDump
    )
  }

  private fun DualSourceProvider.readAllRecords(): MutableList<HprofRecord> {
    val readRecords = mutableListOf<HprofRecord>()
    StreamingHprofReader.readerFor(this).asStreamingRecordReader()
      .readRecords(setOf(HprofRecord::class)) { position, record ->
        readRecords += record
      }
    return readRecords
  }

  companion object {
    const val MAGIC_WAND_CLASS_NAME = "com.example.MagicWand"
    const val BAGUETTE_CLASS_NAME = "com.example.Baguette"
    const val ANSWER_FIELD_NAME = "answer"
    const val TREASURE_CHEST_CLASS_NAME = "com.example.TreasureChest"
    const val CONTENT_FIELD_NAME = "content"
  }
}