summaryrefslogtreecommitdiff
path: root/tools/ahat/test/InstanceTest.java
diff options
context:
space:
mode:
author Richard Uhler <ruhler@google.com> 2017-02-21 10:54:51 +0000
committer Gerrit Code Review <noreply-gerritcodereview@google.com> 2017-02-21 10:54:52 +0000
commit89bed6d6fcd687cfedd10c14927c104eddf99c7f (patch)
treec044cdb91c15a4827cb1a15a6b35cd6b7ef9e37b /tools/ahat/test/InstanceTest.java
parent8ca86eae1f6030782b2646b5b5b0976e06227233 (diff)
parentd640e29f9dad93f51e74026327dd53bb5a30eb33 (diff)
Merge changes Ic39b6d55,Id9a392ac,I1a6b05ea
* changes: Show unreachable objects in ahat. ahat: add support for diffing two heap dumps. Refactor ahat's perflib api.
Diffstat (limited to 'tools/ahat/test/InstanceTest.java')
-rw-r--r--tools/ahat/test/InstanceTest.java413
1 files changed, 413 insertions, 0 deletions
diff --git a/tools/ahat/test/InstanceTest.java b/tools/ahat/test/InstanceTest.java
new file mode 100644
index 0000000000..3a50150c0e
--- /dev/null
+++ b/tools/ahat/test/InstanceTest.java
@@ -0,0 +1,413 @@
+/*
+ * 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());
+ }
+}