Generate an hprof file to test ahat.

This change sets up the infrastructure to write test cases for ahat
that make use of an hprof file automatically generated from a sample
program.

Change-Id: Id11f656afb69c96a26655cc4caeb745ad844f431
diff --git a/tools/ahat/Android.mk b/tools/ahat/Android.mk
index 3c1522c..71366c1 100644
--- a/tools/ahat/Android.mk
+++ b/tools/ahat/Android.mk
@@ -16,6 +16,8 @@
 
 LOCAL_PATH := $(call my-dir)
 
+include art/build/Android.common_test.mk
+
 # --- ahat.jar ----------------
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -44,7 +46,7 @@
 
 ahat: $(LOCAL_BUILT_MODULE)
 
-# --- ahat-test.jar --------------
+# --- ahat-tests.jar --------------
 include $(CLEAR_VARS)
 LOCAL_SRC_FILES := $(call all-java-files-under, test)
 LOCAL_JAR_MANIFEST := test/manifest.txt
@@ -53,6 +55,42 @@
 LOCAL_MODULE_TAGS := tests
 LOCAL_MODULE := ahat-tests
 include $(BUILD_HOST_JAVA_LIBRARY)
+AHAT_TEST_JAR := $(LOCAL_BUILT_MODULE)
 
-ahat-test: $(LOCAL_BUILT_MODULE)
-	java -jar $<
+# --- ahat-test-dump.jar --------------
+include $(CLEAR_VARS)
+LOCAL_MODULE := ahat-test-dump
+LOCAL_MODULE_TAGS := tests
+LOCAL_SRC_FILES := $(call all-java-files-under, test-dump)
+include $(BUILD_HOST_DALVIK_JAVA_LIBRARY)
+
+# Determine the location of the test-dump.jar and test-dump.hprof files.
+# These use variables set implicitly by the include of
+# BUILD_HOST_DALVIK_JAVA_LIBRARY above.
+AHAT_TEST_DUMP_JAR := $(LOCAL_BUILT_MODULE)
+AHAT_TEST_DUMP_HPROF := $(intermediates.COMMON)/test-dump.hprof
+
+# Run ahat-test-dump.jar to generate test-dump.hprof
+AHAT_TEST_DUMP_DEPENDENCIES := \
+	$(ART_HOST_EXECUTABLES) \
+	$(HOST_OUT_EXECUTABLES)/art \
+	$(HOST_CORE_IMG_OUT_BASE)$(CORE_IMG_SUFFIX)
+
+$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_ART := $(HOST_OUT_EXECUTABLES)/art
+$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_DUMP_JAR := $(AHAT_TEST_DUMP_JAR)
+$(AHAT_TEST_DUMP_HPROF): PRIVATE_AHAT_TEST_DUMP_DEPENDENCIES := $(AHAT_TEST_DUMP_DEPENDENCIES)
+$(AHAT_TEST_DUMP_HPROF): $(AHAT_TEST_DUMP_JAR) $(AHAT_TEST_DUMP_DEPENDENCIES)
+	$(PRIVATE_AHAT_TEST_ART) -cp $(PRIVATE_AHAT_TEST_DUMP_JAR) Main $@
+
+.PHONY: ahat-test
+ahat-test: PRIVATE_AHAT_TEST_DUMP_HPROF := $(AHAT_TEST_DUMP_HPROF)
+ahat-test: PRIVATE_AHAT_TEST_JAR := $(AHAT_TEST_JAR)
+ahat-test: $(AHAT_TEST_JAR) $(AHAT_TEST_DUMP_HPROF)
+	java -Dahat.test.dump.hprof=$(PRIVATE_AHAT_TEST_DUMP_HPROF) -jar $(PRIVATE_AHAT_TEST_JAR)
+
+# Clean up local variables.
+AHAT_TEST_DUMP_DEPENDENCIES :=
+AHAT_TEST_DUMP_HPROF :=
+AHAT_TEST_DUMP_JAR :=
+AHAT_TEST_JAR :=
+
diff --git a/tools/ahat/src/AhatSnapshot.java b/tools/ahat/src/AhatSnapshot.java
index 2437d03..3035ef7 100644
--- a/tools/ahat/src/AhatSnapshot.java
+++ b/tools/ahat/src/AhatSnapshot.java
@@ -18,13 +18,18 @@
 
 import com.android.tools.perflib.heap.ClassObj;
 import com.android.tools.perflib.heap.Heap;
+import com.android.tools.perflib.heap.HprofParser;
 import com.android.tools.perflib.heap.Instance;
 import com.android.tools.perflib.heap.RootObj;
 import com.android.tools.perflib.heap.Snapshot;
 import com.android.tools.perflib.heap.StackFrame;
 import com.android.tools.perflib.heap.StackTrace;
+import com.android.tools.perflib.heap.io.HprofBuffer;
+import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -47,7 +52,22 @@
   private Site mRootSite;
   private Map<Heap, Long> mHeapSizes;
 
-  public AhatSnapshot(Snapshot snapshot) {
+  /**
+   * Create an AhatSnapshot from an hprof file.
+   */
+  public static AhatSnapshot fromHprof(File hprof) throws IOException {
+    HprofBuffer buffer = new MemoryMappedFileBuffer(hprof);
+    Snapshot snapshot = (new HprofParser(buffer)).parse();
+    snapshot.computeDominators();
+    return new AhatSnapshot(snapshot);
+  }
+
+  /**
+   * Construct an AhatSnapshot for the given perflib snapshot.
+   * Ther user is responsible for calling snapshot.computeDominators before
+   * calling this AhatSnapshot constructor.
+   */
+  private AhatSnapshot(Snapshot snapshot) {
     mSnapshot = snapshot;
     mHeaps = new ArrayList<Heap>(mSnapshot.getHeaps());
     mDominated = new HashMap<Instance, List<Instance>>();
@@ -92,6 +112,11 @@
     }
   }
 
+  // Note: This method is exposed for testing purposes.
+  public ClassObj findClass(String name) {
+    return mSnapshot.findClass(name);
+  }
+
   public Instance findInstance(long id) {
     return mSnapshot.findInstance(id);
   }
diff --git a/tools/ahat/src/InstanceUtils.java b/tools/ahat/src/InstanceUtils.java
index 7ee3ff2..a6ac3b8 100644
--- a/tools/ahat/src/InstanceUtils.java
+++ b/tools/ahat/src/InstanceUtils.java
@@ -32,7 +32,7 @@
    * given name.
    */
   public static boolean isInstanceOfClass(Instance inst, String className) {
-    ClassObj cls = inst.getClassObj();
+    ClassObj cls = (inst == null) ? null : inst.getClassObj();
     return (cls != null && className.equals(cls.getClassName()));
   }
 
@@ -132,7 +132,7 @@
    * Read a field of an instance.
    * Returns null if the field value is null or if the field couldn't be read.
    */
-  private static Object getField(Instance inst, String fieldName) {
+  public static Object getField(Instance inst, String fieldName) {
     if (!(inst instanceof ClassInstance)) {
       return null;
     }
diff --git a/tools/ahat/src/Main.java b/tools/ahat/src/Main.java
index 2e2ddd2..1563aa0 100644
--- a/tools/ahat/src/Main.java
+++ b/tools/ahat/src/Main.java
@@ -16,10 +16,6 @@
 
 package com.android.ahat;
 
-import com.android.tools.perflib.heap.HprofParser;
-import com.android.tools.perflib.heap.Snapshot;
-import com.android.tools.perflib.heap.io.HprofBuffer;
-import com.android.tools.perflib.heap.io.MemoryMappedFileBuffer;
 import com.sun.net.httpserver.HttpServer;
 import java.io.File;
 import java.io.IOException;
@@ -71,15 +67,8 @@
       return;
     }
 
-    System.out.println("Reading hprof file...");
-    HprofBuffer buffer = new MemoryMappedFileBuffer(hprof);
-    Snapshot snapshot = (new HprofParser(buffer)).parse();
-
-    System.out.println("Computing Dominators...");
-    snapshot.computeDominators();
-
-    System.out.println("Processing snapshot for ahat...");
-    AhatSnapshot ahat = new AhatSnapshot(snapshot);
+    System.out.println("Processing hprof file...");
+    AhatSnapshot ahat = AhatSnapshot.fromHprof(hprof);
 
     InetAddress loopback = InetAddress.getLoopbackAddress();
     InetSocketAddress addr = new InetSocketAddress(loopback, port);
diff --git a/tools/ahat/test-dump/Main.java b/tools/ahat/test-dump/Main.java
new file mode 100644
index 0000000..cea1dc1
--- /dev/null
+++ b/tools/ahat/test-dump/Main.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+import dalvik.system.VMDebug;
+import java.io.IOException;
+
+/**
+ * Program used to create a heap dump for test purposes.
+ */
+public class Main {
+  // Keep a reference to the DumpedStuff instance so that it is not garbage
+  // collected before we take the heap dump.
+  public static DumpedStuff stuff;
+
+  // We will take a heap dump that includes a single instance of this
+  // DumpedStuff class. Objects stored as fields in this class can be easily
+  // found in the hprof dump by searching for the instance of the DumpedStuff
+  // class and reading the desired field.
+  public static class DumpedStuff {
+    public String basicString = "hello, world";
+    public String nullString = null;
+    public Object anObject = new Object();
+  }
+
+  public static void main(String[] args) throws IOException {
+    if (args.length < 1) {
+      System.err.println("no output file specified");
+      return;
+    }
+    String file = args[0];
+
+    // Allocate the instance of DumpedStuff.
+    stuff = new DumpedStuff();
+
+    // Take a heap dump that will include that instance of DumpedStuff.
+    System.err.println("Dumping hprof data to " + file);
+    VMDebug.dumpHprofData(file);
+  }
+}
diff --git a/tools/ahat/test/InstanceUtilsTest.java b/tools/ahat/test/InstanceUtilsTest.java
new file mode 100644
index 0000000..7613df4
--- /dev/null
+++ b/tools/ahat/test/InstanceUtilsTest.java
@@ -0,0 +1,48 @@
+/*
+ * 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.tools.perflib.heap.Instance;
+import java.io.IOException;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import org.junit.Test;
+
+public class InstanceUtilsTest {
+  @Test
+  public void basicString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    Instance str = (Instance)dump.getDumpedThing("basicString");
+    assertEquals("hello, world", InstanceUtils.asString(str));
+  }
+
+  @Test
+  public void nullString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    Instance obj = (Instance)dump.getDumpedThing("nullString");
+    assertNull(InstanceUtils.asString(obj));
+  }
+
+  @Test
+  public void notString() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    Instance obj = (Instance)dump.getDumpedThing("anObject");
+    assertNotNull(obj);
+    assertNull(InstanceUtils.asString(obj));
+  }
+}
diff --git a/tools/ahat/test/TestDump.java b/tools/ahat/test/TestDump.java
new file mode 100644
index 0000000..c3a76e4
--- /dev/null
+++ b/tools/ahat/test/TestDump.java
@@ -0,0 +1,89 @@
+/*
+ * 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.tools.perflib.heap.ClassObj;
+import com.android.tools.perflib.heap.Field;
+import com.android.tools.perflib.heap.Instance;
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * The TestDump class is used to get an AhatSnapshot for the test-dump
+ * program.
+ */
+public class TestDump {
+  // It can take on the order of a second to parse and process the test-dump
+  // hprof. To avoid repeating this overhead for each test case, we cache the
+  // loaded instance of TestDump and reuse it when possible. In theory the
+  // test cases should not be able to modify the cached snapshot in a way that
+  // is visible to other test cases.
+  private static TestDump mCachedTestDump = null;
+
+  private AhatSnapshot mSnapshot = null;
+
+  /**
+   * Load the test-dump.hprof file.
+   * The location of the file is read from the system property
+   * "ahat.test.dump.hprof", which is expected to be set on the command line.
+   * For example:
+   *   java -Dahat.test.dump.hprof=test-dump.hprof -jar ahat-tests.jar
+   *
+   * An IOException is thrown if there is a failure reading the hprof file.
+   */
+  private TestDump() throws IOException {
+      String hprof = System.getProperty("ahat.test.dump.hprof");
+      mSnapshot = AhatSnapshot.fromHprof(new File(hprof));
+  }
+
+  /**
+   * Get the AhatSnapshot for the test dump program.
+   */
+  public AhatSnapshot getAhatSnapshot() {
+    return mSnapshot;
+  }
+
+  /**
+   * Return the value of a field in the DumpedStuff instance in the
+   * snapshot for the test-dump program.
+   */
+  public Object getDumpedThing(String name) {
+    ClassObj main = mSnapshot.findClass("Main");
+    Instance stuff = null;
+    for (Map.Entry<Field, Object> fields : main.getStaticFieldValues().entrySet()) {
+      if ("stuff".equals(fields.getKey().getName())) {
+        stuff = (Instance) fields.getValue();
+      }
+    }
+    return InstanceUtils.getField(stuff, name);
+  }
+
+  /**
+   * Get the test dump.
+   * An IOException is thrown if there is an error reading the test dump hprof
+   * file.
+   * To improve performance, this returns a cached instance of the TestDump
+   * when possible.
+   */
+  public static synchronized TestDump getTestDump() throws IOException {
+    if (mCachedTestDump == null) {
+      mCachedTestDump = new TestDump();
+    }
+    return mCachedTestDump;
+  }
+}
diff --git a/tools/ahat/test/Tests.java b/tools/ahat/test/Tests.java
index fb53d90..bab7121 100644
--- a/tools/ahat/test/Tests.java
+++ b/tools/ahat/test/Tests.java
@@ -22,6 +22,7 @@
   public static void main(String[] args) {
     if (args.length == 0) {
       args = new String[]{
+        "com.android.ahat.InstanceUtilsTest",
         "com.android.ahat.QueryTest",
         "com.android.ahat.SortTest"
       };