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