/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.ahat; import com.android.ahat.heapdump.AhatClassObj; import com.android.ahat.heapdump.AhatHeap; import com.android.ahat.heapdump.AhatInstance; import com.android.ahat.heapdump.AhatSnapshot; import com.android.ahat.heapdump.PathElement; import com.android.ahat.heapdump.Value; import com.android.tools.perflib.heap.hprof.HprofClassDump; import com.android.tools.perflib.heap.hprof.HprofConstant; import com.android.tools.perflib.heap.hprof.HprofDumpRecord; import com.android.tools.perflib.heap.hprof.HprofHeapDump; import com.android.tools.perflib.heap.hprof.HprofInstanceDump; import com.android.tools.perflib.heap.hprof.HprofInstanceField; import com.android.tools.perflib.heap.hprof.HprofLoadClass; import com.android.tools.perflib.heap.hprof.HprofPrimitiveArrayDump; import com.android.tools.perflib.heap.hprof.HprofRecord; import com.android.tools.perflib.heap.hprof.HprofRootDebugger; import com.android.tools.perflib.heap.hprof.HprofStaticField; import com.android.tools.perflib.heap.hprof.HprofStringBuilder; import com.android.tools.perflib.heap.hprof.HprofType; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import java.io.IOException; import java.util.ArrayList; import java.util.List; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class InstanceTest { @Test public void asStringBasic() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("basicString"); assertEquals("hello, world", str.asString()); } @Test public void asStringNonAscii() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); assertEquals("Sigma (Ʃ) is not ASCII", str.asString()); } @Test public void asStringEmbeddedZero() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); assertEquals("embedded\0...", str.asString()); } @Test public void asStringCharArray() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("charArray"); assertEquals("char thing", str.asString()); } @Test public void asStringTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("basicString"); assertEquals("hello", str.asString(5)); } @Test public void asStringTruncatedNonAscii() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); assertEquals("Sigma (Ʃ)", str.asString(9)); } @Test public void asStringTruncatedEmbeddedZero() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); assertEquals("embed", str.asString(5)); } @Test public void asStringCharArrayTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("charArray"); assertEquals("char ", str.asString(5)); } @Test public void asStringExactMax() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("basicString"); assertEquals("hello, world", str.asString(12)); } @Test public void asStringExactMaxNonAscii() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); assertEquals("Sigma (Ʃ) is not ASCII", str.asString(22)); } @Test public void asStringExactMaxEmbeddedZero() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); assertEquals("embedded\0...", str.asString(12)); } @Test public void asStringCharArrayExactMax() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("charArray"); assertEquals("char thing", str.asString(10)); } @Test public void asStringNotTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("basicString"); assertEquals("hello, world", str.asString(50)); } @Test public void asStringNotTruncatedNonAscii() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); assertEquals("Sigma (Ʃ) is not ASCII", str.asString(50)); } @Test public void asStringNotTruncatedEmbeddedZero() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); assertEquals("embedded\0...", str.asString(50)); } @Test public void asStringCharArrayNotTruncated() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("charArray"); assertEquals("char thing", str.asString(50)); } @Test public void asStringNegativeMax() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("basicString"); assertEquals("hello, world", str.asString(-3)); } @Test public void asStringNegativeMaxNonAscii() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("nonAscii"); assertEquals("Sigma (Ʃ) is not ASCII", str.asString(-3)); } @Test public void asStringNegativeMaxEmbeddedZero() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("embeddedZero"); assertEquals("embedded\0...", str.asString(-3)); } @Test public void asStringCharArrayNegativeMax() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance str = dump.getDumpedAhatInstance("charArray"); assertEquals("char thing", str.asString(-3)); } @Test public void asStringNull() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("nullString"); assertNull(obj); } @Test public void asStringNotString() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("anObject"); assertNotNull(obj); assertNull(obj.asString()); } @Test public void basicReference() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance pref = dump.getDumpedAhatInstance("aPhantomReference"); AhatInstance wref = dump.getDumpedAhatInstance("aWeakReference"); AhatInstance nref = dump.getDumpedAhatInstance("aNullReferentReference"); AhatInstance referent = dump.getDumpedAhatInstance("anObject"); assertNotNull(pref); assertNotNull(wref); assertNotNull(nref); assertNotNull(referent); assertEquals(referent, pref.getReferent()); assertEquals(referent, wref.getReferent()); assertNull(nref.getReferent()); assertNull(referent.getReferent()); } @Test public void unreachableReferent() throws IOException { // The test dump program should never be under enough GC pressure for the // soft reference to be cleared. Ensure that ahat will show the soft // reference as having a non-null referent. TestDump dump = TestDump.getTestDump(); AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference"); assertNotNull(ref.getReferent()); } @Test public void gcRootPath() throws IOException { TestDump dump = TestDump.getTestDump(); AhatClassObj main = dump.getAhatSnapshot().findClass("Main"); AhatInstance gcPathArray = dump.getDumpedAhatInstance("gcPathArray"); Value value = gcPathArray.asArrayInstance().getValue(2); AhatInstance base = value.asAhatInstance(); AhatInstance left = base.getRefField("left"); AhatInstance right = base.getRefField("right"); AhatInstance target = left.getRefField("right"); List<PathElement> path = target.getPathFromGcRoot(); assertEquals(6, path.size()); assertEquals(main, path.get(0).instance); assertEquals(".stuff", path.get(0).field); assertTrue(path.get(0).isDominator); assertEquals(".gcPathArray", path.get(1).field); assertTrue(path.get(1).isDominator); assertEquals(gcPathArray, path.get(2).instance); assertEquals("[2]", path.get(2).field); assertTrue(path.get(2).isDominator); assertEquals(base, path.get(3).instance); assertTrue(path.get(3).isDominator); // There are two possible paths. Either it can go through the 'left' node, // or the 'right' node. if (path.get(3).field.equals(".left")) { assertEquals(".left", path.get(3).field); assertEquals(left, path.get(4).instance); assertEquals(".right", path.get(4).field); assertFalse(path.get(4).isDominator); } else { assertEquals(".right", path.get(3).field); assertEquals(right, path.get(4).instance); assertEquals(".left", path.get(4).field); assertFalse(path.get(4).isDominator); } assertEquals(target, path.get(5).instance); assertEquals("", path.get(5).field); assertTrue(path.get(5).isDominator); } @Test public void retainedSize() throws IOException { TestDump dump = TestDump.getTestDump(); // anObject should not be an immediate dominator of any other object. This // means its retained size should be equal to its size for the heap it was // allocated on, and should be 0 for all other heaps. AhatInstance anObject = dump.getDumpedAhatInstance("anObject"); AhatSnapshot snapshot = dump.getAhatSnapshot(); long size = anObject.getSize(); assertEquals(size, anObject.getTotalRetainedSize()); assertEquals(size, anObject.getRetainedSize(anObject.getHeap())); for (AhatHeap heap : snapshot.getHeaps()) { if (!heap.equals(anObject.getHeap())) { assertEquals(String.format("For heap '%s'", heap.getName()), 0, anObject.getRetainedSize(heap)); } } } @Test public void objectNotABitmap() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("anObject"); assertNull(obj.asBitmap()); } @Test public void arrayNotABitmap() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray"); assertNull(obj.asBitmap()); } @Test public void classObjNotABitmap() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getAhatSnapshot().findClass("Main"); assertNull(obj.asBitmap()); } @Test public void classInstanceToString() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("aPhantomReference"); long id = obj.getId(); assertEquals(String.format("java.lang.ref.PhantomReference@%08x", id), obj.toString()); } @Test public void classObjToString() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getAhatSnapshot().findClass("Main"); assertEquals("Main", obj.toString()); } @Test public void arrayInstanceToString() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray"); long id = obj.getId(); // There's a bug in perfib's proguard deobfuscation for arrays. // To work around that bug for the time being, only test the suffix of // the toString result. Ideally we test for string equality against // "Main$ObjectTree[4]@%08x", id. assertTrue(obj.toString().endsWith(String.format("[4]@%08x", id))); } @Test public void primArrayInstanceToString() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("bigArray"); long id = obj.getId(); assertEquals(String.format("byte[1000000]@%08x", id), obj.toString()); } @Test public void isNotRoot() throws IOException { TestDump dump = TestDump.getTestDump(); AhatInstance obj = dump.getDumpedAhatInstance("anObject"); assertFalse(obj.isRoot()); assertNull(obj.getRootTypes()); } @Test public void asStringEmbedded() throws IOException { // Set up a heap dump with an instance of java.lang.String of // "hello" with instance id 0x42 that is backed by a char array that is // bigger. This is how ART used to represent strings, and we should still // support it in case the heap dump is from a previous platform version. HprofStringBuilder strings = new HprofStringBuilder(0); List<HprofRecord> records = new ArrayList<HprofRecord>(); List<HprofDumpRecord> dump = new ArrayList<HprofDumpRecord>(); final int stringClassObjectId = 1; records.add(new HprofLoadClass(0, 0, stringClassObjectId, 0, strings.get("java.lang.String"))); dump.add(new HprofClassDump(stringClassObjectId, 0, 0, 0, 0, 0, 0, 0, 0, new HprofConstant[0], new HprofStaticField[0], new HprofInstanceField[]{ new HprofInstanceField(strings.get("count"), HprofType.TYPE_INT), new HprofInstanceField(strings.get("hashCode"), HprofType.TYPE_INT), new HprofInstanceField(strings.get("offset"), HprofType.TYPE_INT), new HprofInstanceField(strings.get("value"), HprofType.TYPE_OBJECT)})); dump.add(new HprofPrimitiveArrayDump(0x41, 0, HprofType.TYPE_CHAR, new long[]{'n', 'o', 't', ' ', 'h', 'e', 'l', 'l', 'o', 'o', 'p'})); ByteArrayDataOutput values = ByteStreams.newDataOutput(); values.writeInt(5); // count values.writeInt(0); // hashCode values.writeInt(4); // offset values.writeInt(0x41); // value dump.add(new HprofInstanceDump(0x42, 0, stringClassObjectId, values.toByteArray())); dump.add(new HprofRootDebugger(stringClassObjectId)); dump.add(new HprofRootDebugger(0x42)); records.add(new HprofHeapDump(0, dump.toArray(new HprofDumpRecord[0]))); AhatSnapshot snapshot = SnapshotBuilder.makeSnapshot(strings, records); AhatInstance chars = snapshot.findInstance(0x41); assertNotNull(chars); assertEquals("not helloop", chars.asString()); AhatInstance stringInstance = snapshot.findInstance(0x42); assertNotNull(stringInstance); assertEquals("hello", stringInstance.asString()); } }