xref: /aosp_15_r20/external/leakcanary2/shark-graph/src/test/java/shark/HprofWriterTest.kt (revision d9e8da70d8c9df9a41d7848ae506fb3115cae6e6)

<lambda>null1 package shark
2 
3 import org.assertj.core.api.Assertions.assertThat
4 import org.junit.Test
5 import shark.HprofHeapGraph.Companion.openHeapGraph
6 import shark.HprofRecord.HeapDumpEndRecord
7 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord
8 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.FieldRecord
9 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.ClassDumpRecord.StaticFieldRecord
10 import shark.HprofRecord.HeapDumpRecord.ObjectRecord.InstanceDumpRecord
11 import shark.HprofRecord.LoadClassRecord
12 import shark.HprofRecord.StringRecord
13 import shark.StreamingRecordReaderAdapter.Companion.asStreamingRecordReader
14 import shark.ValueHolder.BooleanHolder
15 import shark.ValueHolder.IntHolder
16 import shark.ValueHolder.ReferenceHolder
17 
18 class HprofWriterTest {
19 
20   private var lastId = 0L
21   private val id: Long
22     get() = ++lastId
23 
24   @Test
25   fun writeAndReadStringRecord() {
26     val record = StringRecord(id, MAGIC_WAND_CLASS_NAME)
27     val bytes = listOf(record).asHprofBytes()
28 
29     val readRecords = bytes.readAllRecords()
30 
31     assertThat(readRecords).hasSize(1)
32     assertThat(readRecords[0]).isInstanceOf(StringRecord::class.java)
33     assertThat((readRecords[0] as StringRecord).id).isEqualTo(record.id)
34     assertThat((readRecords[0] as StringRecord).string).isEqualTo(record.string)
35   }
36 
37   @Test
38   fun writeAndReadClassRecord() {
39     val className = StringRecord(id, MAGIC_WAND_CLASS_NAME)
40     val loadClassRecord = LoadClassRecord(1, id, 1, className.id)
41     val classDump = ClassDumpRecord(
42       id = loadClassRecord.id,
43       stackTraceSerialNumber = 1,
44       superclassId = 0,
45       classLoaderId = 0,
46       signersId = 0,
47       protectionDomainId = 0,
48       instanceSize = 0,
49       staticFields = emptyList(),
50       fields = emptyList()
51     )
52 
53     val bytes = listOf(className, loadClassRecord, classDump).asHprofBytes()
54     bytes.openHeapGraph().use { graph: HeapGraph ->
55       assertThat(graph.findClassByName(className.string)).isNotNull
56     }
57   }
58 
59   @Test
60   fun writeAndReadStaticField() {
61     val className = StringRecord(id, MAGIC_WAND_CLASS_NAME)
62     val field1Name = StringRecord(id, "field1")
63     val field2Name = StringRecord(id, "field2")
64     val loadClassRecord = LoadClassRecord(1, id, 1, className.id)
65     val classDump = ClassDumpRecord(
66       id = loadClassRecord.id,
67       stackTraceSerialNumber = 1,
68       superclassId = 0,
69       classLoaderId = 0,
70       signersId = 0,
71       protectionDomainId = 0,
72       instanceSize = 0,
73       staticFields = listOf(
74         StaticFieldRecord(field1Name.id, PrimitiveType.BOOLEAN.hprofType, BooleanHolder(true)),
75         StaticFieldRecord(field2Name.id, PrimitiveType.INT.hprofType, IntHolder(42))
76       ),
77       fields = emptyList()
78     )
79     val bytes = listOf(className, field1Name, field2Name, loadClassRecord, classDump)
80       .asHprofBytes()
81     bytes.openHeapGraph().use { graph: HeapGraph ->
82       val heapClass = graph.findClassByName(className.string)!!
83       val staticFields = heapClass.readStaticFields().toList()
84       assertThat(staticFields).hasSize(2)
85       assertThat(staticFields[0].name).isEqualTo(field1Name.string)
86       assertThat(staticFields[0].value.asBoolean).isEqualTo(true)
87       assertThat(staticFields[1].name).isEqualTo(field2Name.string)
88       assertThat(staticFields[1].value.asInt).isEqualTo(42)
89     }
90   }
91 
92   @Test
93   fun writeAndReadHprof() {
94     val records = createRecords()
95 
96     val bytes = records.asHprofBytes()
97 
98     val readRecords = bytes.readAllRecords()
99     assertThat(readRecords).hasSameSizeAs(records + HeapDumpEndRecord)
100 
101     bytes.openHeapGraph().use { graph: HeapGraph ->
102       val treasureChestClass = graph.findClassByName(
103         TREASURE_CHEST_CLASS_NAME
104       )!!
105       val baguetteInstance =
106         treasureChestClass[CONTENT_FIELD_NAME]!!.value.asObject!!.asInstance!!
107 
108       assertThat(
109         baguetteInstance[BAGUETTE_CLASS_NAME, ANSWER_FIELD_NAME]!!.value.asInt!!
110       ).isEqualTo(42)
111     }
112   }
113 
114   private fun createRecords(): List<HprofRecord> {
115     val magicWandClassName = StringRecord(id, MAGIC_WAND_CLASS_NAME)
116     val baguetteClassName = StringRecord(id, BAGUETTE_CLASS_NAME)
117     val answerFieldName = StringRecord(id, ANSWER_FIELD_NAME)
118     val treasureChestClassName = StringRecord(
119       id,
120       TREASURE_CHEST_CLASS_NAME
121     )
122     val contentFieldName = StringRecord(id, CONTENT_FIELD_NAME)
123     val loadMagicWandClass = LoadClassRecord(1, id, 1, magicWandClassName.id)
124     val loadBaguetteClass = LoadClassRecord(1, id, 1, baguetteClassName.id)
125     val loadTreasureChestClass = LoadClassRecord(1, id, 1, treasureChestClassName.id)
126     val magicWandClassDump = ClassDumpRecord(
127       id = loadMagicWandClass.id,
128       stackTraceSerialNumber = 1,
129       superclassId = 0,
130       classLoaderId = 0,
131       signersId = 0,
132       protectionDomainId = 0,
133       instanceSize = 0,
134       staticFields = emptyList(),
135       fields = emptyList()
136     )
137     val baguetteClassDump = ClassDumpRecord(
138       id = loadBaguetteClass.id,
139       stackTraceSerialNumber = 1,
140       superclassId = loadMagicWandClass.id,
141       classLoaderId = 0,
142       signersId = 0,
143       protectionDomainId = 0,
144       instanceSize = 0,
145       staticFields = emptyList(),
146       fields = listOf(FieldRecord(answerFieldName.id, PrimitiveType.INT.hprofType))
147     )
148 
149     val baguetteInstanceDump = InstanceDumpRecord(
150       id = id,
151       stackTraceSerialNumber = 1,
152       classId = loadBaguetteClass.id,
153       fieldValues = byteArrayOf(0x0, 0x0, 0x0, 0x2a)
154     )
155 
156     val treasureChestClassDump = ClassDumpRecord(
157       id = loadTreasureChestClass.id,
158       stackTraceSerialNumber = 1,
159       superclassId = 0,
160       classLoaderId = 0,
161       signersId = 0,
162       protectionDomainId = 0,
163       instanceSize = 0,
164       staticFields = listOf(
165         StaticFieldRecord(
166           contentFieldName.id, PrimitiveType.REFERENCE_HPROF_TYPE,
167           ReferenceHolder(baguetteInstanceDump.id)
168         )
169       ),
170       fields = emptyList()
171     )
172 
173     return listOf(
174       magicWandClassName, baguetteClassName, answerFieldName, treasureChestClassName,
175       contentFieldName, loadMagicWandClass,
176       loadBaguetteClass, loadTreasureChestClass,
177       magicWandClassDump, baguetteClassDump, baguetteInstanceDump, treasureChestClassDump
178     )
179   }
180 
181   private fun DualSourceProvider.readAllRecords(): MutableList<HprofRecord> {
182     val readRecords = mutableListOf<HprofRecord>()
183     StreamingHprofReader.readerFor(this).asStreamingRecordReader()
184       .readRecords(setOf(HprofRecord::class)) { position, record ->
185         readRecords += record
186       }
187     return readRecords
188   }
189 
190   companion object {
191     const val MAGIC_WAND_CLASS_NAME = "com.example.MagicWand"
192     const val BAGUETTE_CLASS_NAME = "com.example.Baguette"
193     const val ANSWER_FIELD_NAME = "answer"
194     const val TREASURE_CHEST_CLASS_NAME = "com.example.TreasureChest"
195     const val CONTENT_FIELD_NAME = "content"
196   }
197 }
198