blob: e05782c0b60d853f666ffac0a216ce675898fe05 [file] [log] [blame]
Richard Uhlercda4f2e2016-09-09 09:56:20 +01001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.ahat;
18
19import com.android.ahat.heapdump.AhatClassObj;
20import com.android.ahat.heapdump.AhatHeap;
21import com.android.ahat.heapdump.AhatInstance;
22import com.android.ahat.heapdump.AhatSnapshot;
23import com.android.ahat.heapdump.PathElement;
Richard Uhler3ee4bff2017-05-16 13:31:01 +010024import com.android.ahat.heapdump.Size;
Richard Uhlercda4f2e2016-09-09 09:56:20 +010025import com.android.ahat.heapdump.Value;
26import com.android.tools.perflib.heap.hprof.HprofClassDump;
27import com.android.tools.perflib.heap.hprof.HprofConstant;
28import com.android.tools.perflib.heap.hprof.HprofDumpRecord;
29import com.android.tools.perflib.heap.hprof.HprofHeapDump;
30import com.android.tools.perflib.heap.hprof.HprofInstanceDump;
31import com.android.tools.perflib.heap.hprof.HprofInstanceField;
32import com.android.tools.perflib.heap.hprof.HprofLoadClass;
33import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump;
34import com.android.tools.perflib.heap.hprof.HprofRecord;
35import com.android.tools.perflib.heap.hprof.HprofRootDebugger;
36import com.android.tools.perflib.heap.hprof.HprofStaticField;
37import com.android.tools.perflib.heap.hprof.HprofStringBuilder;
38import com.android.tools.perflib.heap.hprof.HprofType;
39import com.google.common.io.ByteArrayDataOutput;
40import com.google.common.io.ByteStreams;
41import java.io.IOException;
42import java.util.ArrayList;
43import java.util.List;
44import org.junit.Test;
45
46import static org.junit.Assert.assertEquals;
47import static org.junit.Assert.assertFalse;
48import static org.junit.Assert.assertNotNull;
49import static org.junit.Assert.assertNull;
50import static org.junit.Assert.assertTrue;
51
52public class InstanceTest {
53 @Test
54 public void asStringBasic() throws IOException {
55 TestDump dump = TestDump.getTestDump();
56 AhatInstance str = dump.getDumpedAhatInstance("basicString");
57 assertEquals("hello, world", str.asString());
58 }
59
60 @Test
61 public void asStringNonAscii() throws IOException {
62 TestDump dump = TestDump.getTestDump();
63 AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
64 assertEquals("Sigma (Ʃ) is not ASCII", str.asString());
65 }
66
67 @Test
68 public void asStringEmbeddedZero() throws IOException {
69 TestDump dump = TestDump.getTestDump();
70 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
71 assertEquals("embedded\0...", str.asString());
72 }
73
74 @Test
75 public void asStringCharArray() throws IOException {
76 TestDump dump = TestDump.getTestDump();
77 AhatInstance str = dump.getDumpedAhatInstance("charArray");
78 assertEquals("char thing", str.asString());
79 }
80
81 @Test
82 public void asStringTruncated() throws IOException {
83 TestDump dump = TestDump.getTestDump();
84 AhatInstance str = dump.getDumpedAhatInstance("basicString");
85 assertEquals("hello", str.asString(5));
86 }
87
88 @Test
89 public void asStringTruncatedNonAscii() throws IOException {
90 TestDump dump = TestDump.getTestDump();
91 AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
92 assertEquals("Sigma (Ʃ)", str.asString(9));
93 }
94
95 @Test
96 public void asStringTruncatedEmbeddedZero() throws IOException {
97 TestDump dump = TestDump.getTestDump();
98 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
99 assertEquals("embed", str.asString(5));
100 }
101
102 @Test
103 public void asStringCharArrayTruncated() throws IOException {
104 TestDump dump = TestDump.getTestDump();
105 AhatInstance str = dump.getDumpedAhatInstance("charArray");
106 assertEquals("char ", str.asString(5));
107 }
108
109 @Test
110 public void asStringExactMax() throws IOException {
111 TestDump dump = TestDump.getTestDump();
112 AhatInstance str = dump.getDumpedAhatInstance("basicString");
113 assertEquals("hello, world", str.asString(12));
114 }
115
116 @Test
117 public void asStringExactMaxNonAscii() throws IOException {
118 TestDump dump = TestDump.getTestDump();
119 AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
120 assertEquals("Sigma (Ʃ) is not ASCII", str.asString(22));
121 }
122
123 @Test
124 public void asStringExactMaxEmbeddedZero() throws IOException {
125 TestDump dump = TestDump.getTestDump();
126 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
127 assertEquals("embedded\0...", str.asString(12));
128 }
129
130 @Test
131 public void asStringCharArrayExactMax() throws IOException {
132 TestDump dump = TestDump.getTestDump();
133 AhatInstance str = dump.getDumpedAhatInstance("charArray");
134 assertEquals("char thing", str.asString(10));
135 }
136
137 @Test
138 public void asStringNotTruncated() throws IOException {
139 TestDump dump = TestDump.getTestDump();
140 AhatInstance str = dump.getDumpedAhatInstance("basicString");
141 assertEquals("hello, world", str.asString(50));
142 }
143
144 @Test
145 public void asStringNotTruncatedNonAscii() throws IOException {
146 TestDump dump = TestDump.getTestDump();
147 AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
148 assertEquals("Sigma (Ʃ) is not ASCII", str.asString(50));
149 }
150
151 @Test
152 public void asStringNotTruncatedEmbeddedZero() throws IOException {
153 TestDump dump = TestDump.getTestDump();
154 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
155 assertEquals("embedded\0...", str.asString(50));
156 }
157
158 @Test
159 public void asStringCharArrayNotTruncated() throws IOException {
160 TestDump dump = TestDump.getTestDump();
161 AhatInstance str = dump.getDumpedAhatInstance("charArray");
162 assertEquals("char thing", str.asString(50));
163 }
164
165 @Test
166 public void asStringNegativeMax() throws IOException {
167 TestDump dump = TestDump.getTestDump();
168 AhatInstance str = dump.getDumpedAhatInstance("basicString");
169 assertEquals("hello, world", str.asString(-3));
170 }
171
172 @Test
173 public void asStringNegativeMaxNonAscii() throws IOException {
174 TestDump dump = TestDump.getTestDump();
175 AhatInstance str = dump.getDumpedAhatInstance("nonAscii");
176 assertEquals("Sigma (Ʃ) is not ASCII", str.asString(-3));
177 }
178
179 @Test
180 public void asStringNegativeMaxEmbeddedZero() throws IOException {
181 TestDump dump = TestDump.getTestDump();
182 AhatInstance str = dump.getDumpedAhatInstance("embeddedZero");
183 assertEquals("embedded\0...", str.asString(-3));
184 }
185
186 @Test
187 public void asStringCharArrayNegativeMax() throws IOException {
188 TestDump dump = TestDump.getTestDump();
189 AhatInstance str = dump.getDumpedAhatInstance("charArray");
190 assertEquals("char thing", str.asString(-3));
191 }
192
193 @Test
194 public void asStringNull() throws IOException {
195 TestDump dump = TestDump.getTestDump();
196 AhatInstance obj = dump.getDumpedAhatInstance("nullString");
197 assertNull(obj);
198 }
199
200 @Test
201 public void asStringNotString() throws IOException {
202 TestDump dump = TestDump.getTestDump();
203 AhatInstance obj = dump.getDumpedAhatInstance("anObject");
204 assertNotNull(obj);
205 assertNull(obj.asString());
206 }
207
208 @Test
209 public void basicReference() throws IOException {
210 TestDump dump = TestDump.getTestDump();
211
212 AhatInstance pref = dump.getDumpedAhatInstance("aPhantomReference");
213 AhatInstance wref = dump.getDumpedAhatInstance("aWeakReference");
214 AhatInstance nref = dump.getDumpedAhatInstance("aNullReferentReference");
215 AhatInstance referent = dump.getDumpedAhatInstance("anObject");
216 assertNotNull(pref);
217 assertNotNull(wref);
218 assertNotNull(nref);
219 assertNotNull(referent);
220 assertEquals(referent, pref.getReferent());
221 assertEquals(referent, wref.getReferent());
222 assertNull(nref.getReferent());
223 assertNull(referent.getReferent());
224 }
225
226 @Test
Richard Uhlerd640e292016-12-28 15:46:03 +0000227 public void unreachableReferent() throws IOException {
228 // The test dump program should never be under enough GC pressure for the
229 // soft reference to be cleared. Ensure that ahat will show the soft
230 // reference as having a non-null referent.
231 TestDump dump = TestDump.getTestDump();
232 AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference");
233 assertNotNull(ref.getReferent());
234 }
235
236 @Test
Richard Uhlercda4f2e2016-09-09 09:56:20 +0100237 public void gcRootPath() throws IOException {
238 TestDump dump = TestDump.getTestDump();
239
240 AhatClassObj main = dump.getAhatSnapshot().findClass("Main");
241 AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray");
242 Value value = gcPathArray.asArrayInstance().getValue(2);
243 AhatInstance base = value.asAhatInstance();
244 AhatInstance left = base.getRefField("left");
245 AhatInstance right = base.getRefField("right");
246 AhatInstance target = left.getRefField("right");
247
248 List<PathElement> path = target.getPathFromGcRoot();
249 assertEquals(6, path.size());
250
251 assertEquals(main, path.get(0).instance);
252 assertEquals(".stuff", path.get(0).field);
253 assertTrue(path.get(0).isDominator);
254
255 assertEquals(".gcPathArray", path.get(1).field);
256 assertTrue(path.get(1).isDominator);
257
258 assertEquals(gcPathArray, path.get(2).instance);
259 assertEquals("[2]", path.get(2).field);
260 assertTrue(path.get(2).isDominator);
261
262 assertEquals(base, path.get(3).instance);
263 assertTrue(path.get(3).isDominator);
264
265 // There are two possible paths. Either it can go through the 'left' node,
266 // or the 'right' node.
267 if (path.get(3).field.equals(".left")) {
268 assertEquals(".left", path.get(3).field);
269
270 assertEquals(left, path.get(4).instance);
271 assertEquals(".right", path.get(4).field);
272 assertFalse(path.get(4).isDominator);
273
274 } else {
275 assertEquals(".right", path.get(3).field);
276
277 assertEquals(right, path.get(4).instance);
278 assertEquals(".left", path.get(4).field);
279 assertFalse(path.get(4).isDominator);
280 }
281
282 assertEquals(target, path.get(5).instance);
283 assertEquals("", path.get(5).field);
284 assertTrue(path.get(5).isDominator);
285 }
286
287 @Test
Richard Uhlere017aa32017-08-11 10:07:38 +0100288 public void gcRootPathNotWeak() throws IOException {
289 TestDump dump = TestDump.getTestDump();
290
291 AhatInstance strong = dump.getDumpedAhatInstance("aLongStrongPathToSamplePathObject");
292 AhatInstance strong2 = strong.getField("referent").asAhatInstance();
293 AhatInstance object = strong2.getField("referent").asAhatInstance();
294
295 List<PathElement> path = object.getPathFromGcRoot();
296 assertEquals(strong2, path.get(path.size() - 2).instance);
297 }
298
299 @Test
Richard Uhlercda4f2e2016-09-09 09:56:20 +0100300 public void retainedSize() throws IOException {
301 TestDump dump = TestDump.getTestDump();
302
303 // anObject should not be an immediate dominator of any other object. This
304 // means its retained size should be equal to its size for the heap it was
305 // allocated on, and should be 0 for all other heaps.
306 AhatInstance anObject = dump.getDumpedAhatInstance("anObject");
307 AhatSnapshot snapshot = dump.getAhatSnapshot();
Richard Uhler3ee4bff2017-05-16 13:31:01 +0100308 Size size = anObject.getSize();
Richard Uhlercda4f2e2016-09-09 09:56:20 +0100309 assertEquals(size, anObject.getTotalRetainedSize());
310 assertEquals(size, anObject.getRetainedSize(anObject.getHeap()));
311 for (AhatHeap heap : snapshot.getHeaps()) {
312 if (!heap.equals(anObject.getHeap())) {
313 assertEquals(String.format("For heap '%s'", heap.getName()),
Richard Uhler3ee4bff2017-05-16 13:31:01 +0100314 Size.ZERO, anObject.getRetainedSize(heap));
Richard Uhlercda4f2e2016-09-09 09:56:20 +0100315 }
316 }
317 }
318
319 @Test
320 public void objectNotABitmap() throws IOException {
321 TestDump dump = TestDump.getTestDump();
322 AhatInstance obj = dump.getDumpedAhatInstance("anObject");
323 assertNull(obj.asBitmap());
324 }
325
326 @Test
327 public void arrayNotABitmap() throws IOException {
328 TestDump dump = TestDump.getTestDump();
329 AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
330 assertNull(obj.asBitmap());
331 }
332
333 @Test
334 public void classObjNotABitmap() throws IOException {
335 TestDump dump = TestDump.getTestDump();
336 AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
337 assertNull(obj.asBitmap());
338 }
339
340 @Test
341 public void classInstanceToString() throws IOException {
342 TestDump dump = TestDump.getTestDump();
343 AhatInstance obj = dump.getDumpedAhatInstance("aPhantomReference");
344 long id = obj.getId();
345 assertEquals(String.format("java.lang.ref.PhantomReference@%08x", id), obj.toString());
346 }
347
348 @Test
349 public void classObjToString() throws IOException {
350 TestDump dump = TestDump.getTestDump();
351 AhatInstance obj = dump.getAhatSnapshot().findClass("Main");
Richard Uhler26870502017-07-04 15:55:19 +0100352 assertEquals("class Main", obj.toString());
Richard Uhlercda4f2e2016-09-09 09:56:20 +0100353 }
354
355 @Test
356 public void arrayInstanceToString() throws IOException {
357 TestDump dump = TestDump.getTestDump();
358 AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
359 long id = obj.getId();
360
361 // There's a bug in perfib's proguard deobfuscation for arrays.
362 // To work around that bug for the time being, only test the suffix of
363 // the toString result. Ideally we test for string equality against
364 // "Main$ObjectTree[4]@%08x", id.
365 assertTrue(obj.toString().endsWith(String.format("[4]@%08x", id)));
366 }
367
368 @Test
369 public void primArrayInstanceToString() throws IOException {
370 TestDump dump = TestDump.getTestDump();
371 AhatInstance obj = dump.getDumpedAhatInstance("bigArray");
372 long id = obj.getId();
373 assertEquals(String.format("byte[1000000]@%08x", id), obj.toString());
374 }
375
376 @Test
377 public void isNotRoot() throws IOException {
378 TestDump dump = TestDump.getTestDump();
379 AhatInstance obj = dump.getDumpedAhatInstance("anObject");
380 assertFalse(obj.isRoot());
381 assertNull(obj.getRootTypes());
382 }
383
384 @Test
Richard Uhler26870502017-07-04 15:55:19 +0100385 public void reverseReferences() throws IOException {
386 TestDump dump = TestDump.getTestDump();
387 AhatInstance obj = dump.getDumpedAhatInstance("anObject");
388 AhatInstance ref = dump.getDumpedAhatInstance("aReference");
389 AhatInstance weak = dump.getDumpedAhatInstance("aWeakReference");
390 assertTrue(obj.getHardReverseReferences().contains(ref));
391 assertFalse(obj.getHardReverseReferences().contains(weak));
392 assertFalse(obj.getSoftReverseReferences().contains(ref));
393 assertTrue(obj.getSoftReverseReferences().contains(weak));
394 }
395
396 @Test
Richard Uhlercda4f2e2016-09-09 09:56:20 +0100397 public void asStringEmbedded() throws IOException {
398 // Set up a heap dump with an instance of java.lang.String of
399 // "hello" with instance id 0x42 that is backed by a char array that is
400 // bigger. This is how ART used to represent strings, and we should still
401 // support it in case the heap dump is from a previous platform version.
402 HprofStringBuilder strings = new HprofStringBuilder(0);
403 List<HprofRecord> records = new ArrayList<HprofRecord>();
404 List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>();
405
406 final int stringClassObjectId = 1;
407 records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String")));
408 dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0,
409 new HprofConstant[0], new HprofStaticField[0],
410 new HprofInstanceField[]{
411 new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT),
412 new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT),
413 new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT),
414 new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)}));
415
416 dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR,
417 new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'}));
418
419 ByteArrayDataOutput values = ByteStreams.newDataOutput();
420 values.writeInt(5); // count
421 values.writeInt(0); // hashCode
422 values.writeInt(4); // offset
423 values.writeInt(0x41); // value
424 dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray()));
425 dump.add(new HprofRootDebugger(stringClassObjectId));
426 dump.add(new HprofRootDebugger(0x42));
427
428 records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0])));
429 AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records);
430 AhatInstance chars = snapshot.findInstance(0x41);
431 assertNotNull(chars);
432 assertEquals("not helloop", chars.asString());
433
434 AhatInstance stringInstance = snapshot.findInstance(0x42);
435 assertNotNull(stringInstance);
436 assertEquals("hello", stringInstance.asString());
437 }
438}