CTS: Add a JVMTI allocation tracking test

Copy ART run-test for basic tracking functionality to CTS. Change
implementation to compare in code instead of text output.

This reverts commit d4c99081b67c01194b63c4e3bef959b220c3f66a.

Bug: 32072923
Test: m cts
Test: cts-tradefed run cts-dev --module CtsJvmtiTrackingHostTestCases
Change-Id: I105234a5ed5bed66ae3ed7f0cffe99fb6caa9fcf
diff --git a/hostsidetests/jvmti/allocation-tracking/Android.mk b/hostsidetests/jvmti/allocation-tracking/Android.mk
new file mode 100644
index 0000000..c5619ad
--- /dev/null
+++ b/hostsidetests/jvmti/allocation-tracking/Android.mk
@@ -0,0 +1,26 @@
+# Copyright (C) 2014 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsJvmtiTrackingHostTestCases
+LOCAL_STATIC_JAVA_LIBRARIES := CtsJvmtiHostTestBase
+LOCAL_MODULE_TAGS := tests
+LOCAL_COMPATIBILITY_SUITE := cts
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml b/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
new file mode 100644
index 0000000..fc26aae
--- /dev/null
+++ b/hostsidetests/jvmti/allocation-tracking/AndroidTest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for CTS JVMTI test cases">
+    <target_preparer class="android.jvmti.cts.JvmtiPreparer">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsJvmtiTrackingDeviceApp.apk" />
+        <option name="package-name" value="android.jvmti.cts.tracking" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsJvmtiTrackingHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/jvmti/allocation-tracking/app/Android.mk b/hostsidetests/jvmti/allocation-tracking/app/Android.mk
new file mode 100644
index 0000000..71ab1b5
--- /dev/null
+++ b/hostsidetests/jvmti/allocation-tracking/app/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2017 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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_COMPATIBILITY_SUITE := cts
+LOCAL_STATIC_JAVA_LIBRARIES := CtsJvmtiDeviceAppBase
+LOCAL_JNI_SHARED_LIBRARIES := libctsjvmtiagent
+LOCAL_MULTILIB := both
+LOCAL_SDK_VERSION := current
+
+# TODO: Refactor. This is the only thing every changing.
+LOCAL_PACKAGE_NAME := CtsJvmtiTrackingDeviceApp
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/jvmti/allocation-tracking/app/AndroidManifest.xml b/hostsidetests/jvmti/allocation-tracking/app/AndroidManifest.xml
new file mode 100755
index 0000000..76d753c
--- /dev/null
+++ b/hostsidetests/jvmti/allocation-tracking/app/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2017 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.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.jvmti.cts.tracking">
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.jvmti.JvmtiActivity" >
+        </activity>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:label="CTS tests for JVMTI"
+        android:targetPackage="android.jvmti.cts.tracking" >
+    </instrumentation>
+</manifest>
+
diff --git a/hostsidetests/jvmti/allocation-tracking/app/src/android/jvmti/cts/JvmtiTrackingTest.java b/hostsidetests/jvmti/allocation-tracking/app/src/android/jvmti/cts/JvmtiTrackingTest.java
new file mode 100644
index 0000000..70434d1
--- /dev/null
+++ b/hostsidetests/jvmti/allocation-tracking/app/src/android/jvmti/cts/JvmtiTrackingTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 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 android.jvmti.cts;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.ArrayList;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Check tagging-related functionality.
+ */
+public class JvmtiTrackingTest extends JvmtiTestBase {
+
+    @Before
+    public void setUp() throws Exception {
+        // Bind our native methods.
+        JniBindings.bindAgentJNI("android/jvmti/cts/JvmtiTrackingTest",
+                getClass().getClassLoader());
+
+        prefetchClassNames();
+    }
+
+    // Pre-resolve class names so the strings don't have to be allocated as a side effect of
+    // callback printing.
+    private static void prefetchClassNames() {
+        Object.class.getName();
+        Integer.class.getName();
+        Float.class.getName();
+        Short.class.getName();
+        Byte.class.getName();
+        Double.class.getName();
+    }
+
+    private ArrayList<Object> l = new ArrayList<>(100);
+
+    @Test
+    public void testTracking() throws Exception {
+        // Disable the global registration from OnLoad, to get into a known state.
+        enableAllocationTracking(null, false);
+
+        assertEquals(null, getAndResetAllocationTrackingString());
+
+        // Enable actual logging callback.
+        setupObjectAllocCallback(true);
+
+        enableAllocationTracking(null, true);
+
+        l.add(new Object());
+        l.add(new Integer(1));
+
+        enableAllocationTracking(null, false);
+
+        assertEquals(
+                "ObjectAllocated type java.lang.Object/java.lang.Object size 8#"
+                        + "ObjectAllocated type java.lang.Integer/java.lang.Integer size 16#",
+                        getAndResetAllocationTrackingString());
+
+        l.add(new Float(1.0f));
+
+        assertEquals(null, getAndResetAllocationTrackingString());
+
+        enableAllocationTracking(Thread.currentThread(), true);
+
+        l.add(new Short((short) 0));
+
+        enableAllocationTracking(Thread.currentThread(), false);
+
+        assertEquals("ObjectAllocated type java.lang.Short/java.lang.Short size 16#",
+                getAndResetAllocationTrackingString());
+
+        l.add(new Byte((byte) 0));
+
+        assertEquals(null, getAndResetAllocationTrackingString());
+
+        testThread(l, true, true);
+
+        l.add(new Byte((byte) 0));
+
+        assertEquals("ObjectAllocated type java.lang.Double/java.lang.Double size 16#",
+                getAndResetAllocationTrackingString());
+
+        testThread(l, true, false);
+
+        assertEquals("ObjectAllocated type java.lang.Double/java.lang.Double size 16#",
+                getAndResetAllocationTrackingString());
+
+        System.out.println("Tracking on different thread");
+
+        testThread(l, false, true);
+
+        l.add(new Byte((byte) 0));
+
+        // Disable actual logging callback and re-enable tracking, so we can keep the event enabled
+        // and
+        // check that shutdown works correctly.
+        setupObjectAllocCallback(false);
+        enableAllocationTracking(null, true);
+
+        assertEquals(null, getAndResetAllocationTrackingString());
+    }
+
+    private static void testThread(final ArrayList<Object> l, final boolean sameThread,
+            final boolean disableTracking) throws Exception {
+        final SimpleBarrier startBarrier = new SimpleBarrier(1);
+        final SimpleBarrier trackBarrier = new SimpleBarrier(1);
+
+        final Thread thisThread = Thread.currentThread();
+
+        Thread t = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    startBarrier.dec();
+                    trackBarrier.waitFor();
+                } catch (Exception e) {
+                    e.printStackTrace(System.out);
+                    System.exit(1);
+                }
+
+                l.add(new Double(0.0));
+
+                if (disableTracking) {
+                    enableAllocationTracking(sameThread ? this : thisThread, false);
+                }
+            }
+        };
+
+        t.start();
+        startBarrier.waitFor();
+        enableAllocationTracking(sameThread ? t : Thread.currentThread(), true);
+        trackBarrier.dec();
+
+        t.join();
+    }
+
+    // Our own little barrier, to avoid behind-the-scenes allocations.
+    private static class SimpleBarrier {
+        int count;
+
+        public SimpleBarrier(int i) {
+            count = i;
+        }
+
+        public synchronized void dec() throws Exception {
+            count--;
+            notifyAll();
+        }
+
+        public synchronized void waitFor() throws Exception {
+            while (count != 0) {
+                wait();
+            }
+        }
+    }
+
+    private static native void setupObjectAllocCallback(boolean enable);
+
+    private static native void enableAllocationTracking(Thread thread, boolean enable);
+
+    private static native String getAndResetAllocationTrackingString();
+}
diff --git a/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JniBindings.java b/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JniBindings.java
index 822c1aa..cc473d2 100644
--- a/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JniBindings.java
+++ b/hostsidetests/jvmti/base/app/src/android/jvmti/cts/JniBindings.java
@@ -46,4 +46,9 @@
     // Load the given class with the given classloader, and bind all native methods to corresponding
     // C methods in the agent. Will abort if any of the steps fail.
     public static native void bindAgentJNI(String className, ClassLoader classLoader);
+
+    // General functionality shared between tests.
+    public static native void setTag(Object o, long tag);
+
+    public static native long getTag(Object o);
 }
diff --git a/hostsidetests/jvmti/base/jni/Android.mk b/hostsidetests/jvmti/base/jni/Android.mk
index 295f4c8..578bed0 100644
--- a/hostsidetests/jvmti/base/jni/Android.mk
+++ b/hostsidetests/jvmti/base/jni/Android.mk
@@ -28,6 +28,9 @@
 # Tagging.
 LOCAL_SRC_FILES += tagging.cpp
 
+# Tracking.
+LOCAL_SRC_FILES += tracking.cpp
+
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 LOCAL_HEADER_LIBRARIES := libopenjdkjvmti_headers
 
diff --git a/hostsidetests/jvmti/base/jni/tagging.cpp b/hostsidetests/jvmti/base/jni/tagging.cpp
index 7ef996c..1e59e13 100644
--- a/hostsidetests/jvmti/base/jni/tagging.cpp
+++ b/hostsidetests/jvmti/base/jni/tagging.cpp
@@ -28,14 +28,14 @@
 namespace jvmti {
 namespace tagging {
 
-extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiTaggingTest_setTag(
+extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JniBindings_setTag(
     JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject obj, jlong tag) {
   jvmtiEnv* jvmti_env = GetJvmtiEnv();
   jvmtiError ret = jvmti_env->SetTag(obj, tag);
   JvmtiErrorToException(env, jvmti_env, ret);
 }
 
-extern "C" JNIEXPORT jlong JNICALL Java_android_jvmti_cts_JvmtiTaggingTest_getTag(
+extern "C" JNIEXPORT jlong JNICALL Java_android_jvmti_cts_JniBindings_getTag(
     JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject obj) {
   jvmtiEnv* jvmti_env = GetJvmtiEnv();
   jlong tag = 0;
diff --git a/hostsidetests/jvmti/base/jni/tracking.cpp b/hostsidetests/jvmti/base/jni/tracking.cpp
new file mode 100644
index 0000000..a46f491
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/tracking.cpp
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+#include <mutex>
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "common.h"
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+
+namespace cts {
+namespace jvmti {
+namespace allocation_tracking {
+
+static std::string GetClassName(JNIEnv* jni_env, jclass cls) {
+  ScopedLocalRef<jclass> class_class(jni_env, jni_env->GetObjectClass(cls));
+  jmethodID mid = jni_env->GetMethodID(class_class.get(), "getName", "()Ljava/lang/String;");
+  ScopedLocalRef<jstring> str(
+      jni_env, reinterpret_cast<jstring>(jni_env->CallObjectMethod(cls, mid)));
+  ScopedUtfChars utf_chars(jni_env, str.get());
+  return utf_chars.c_str();
+}
+
+static std::mutex gLock;
+static std::string gCollection;
+
+static void JNICALL ObjectAllocated(jvmtiEnv* ti_env ATTRIBUTE_UNUSED,
+                                    JNIEnv* jni_env,
+                                    jthread thread ATTRIBUTE_UNUSED,
+                                    jobject object,
+                                    jclass object_klass,
+                                    jlong size) {
+  std::string object_klass_descriptor = GetClassName(jni_env, object_klass);
+  ScopedLocalRef<jclass> object_klass2(jni_env, jni_env->GetObjectClass(object));
+  std::string object_klass_descriptor2 = GetClassName(jni_env, object_klass2.get());
+  std::string result = android::base::StringPrintf("ObjectAllocated type %s/%s size %zu",
+                                                   object_klass_descriptor.c_str(),
+                                                   object_klass_descriptor2.c_str(),
+                                                   static_cast<size_t>(size));
+  std::unique_lock<std::mutex> mu(gLock);
+  gCollection += result + "#";
+}
+
+extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiTrackingTest_setupObjectAllocCallback(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jboolean enable) {
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.VMObjectAlloc = enable ? ObjectAllocated : nullptr;
+
+  jvmtiError ret = GetJvmtiEnv()->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  JvmtiErrorToException(env, GetJvmtiEnv(), ret);
+}
+
+extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiTrackingTest_enableAllocationTracking(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jthread thread, jboolean enable) {
+  jvmtiError ret = GetJvmtiEnv()->SetEventNotificationMode(
+      enable ? JVMTI_ENABLE : JVMTI_DISABLE,
+      JVMTI_EVENT_VM_OBJECT_ALLOC,
+      thread);
+  JvmtiErrorToException(env, GetJvmtiEnv(), ret);
+}
+
+extern "C" JNIEXPORT
+jstring JNICALL Java_android_jvmti_cts_JvmtiTrackingTest_getAndResetAllocationTrackingString(
+    JNIEnv* env, jclass klass ATTRIBUTE_UNUSED) {
+  // We will have a string allocation. So only do the C++ string retrieval under lock.
+  std::string result;
+  {
+    std::unique_lock<std::mutex> mu(gLock);
+    result.swap(gCollection);
+  }
+
+  if (result.empty()) {
+    return nullptr;
+  }
+
+  return env->NewStringUTF(result.c_str());
+}
+
+}  // namespace allocation_tracking
+}  // namespace jvmti
+}  // namespace cts
diff --git a/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java b/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java
index d2cfe96..6ba9376 100644
--- a/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java
+++ b/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java
@@ -37,28 +37,28 @@
 
     private static WeakReference<Object> test() {
         Object o1 = new Object();
-        setTag(o1, 1);
+        JniBindings.setTag(o1, 1);
 
         Object o2 = new Object();
-        setTag(o2, 2);
+        JniBindings.setTag(o2, 2);
 
-        assertEquals(1, getTag(o1));
-        assertEquals(2, getTag(o2));
+        assertEquals(1, JniBindings.getTag(o1));
+        assertEquals(2, JniBindings.getTag(o2));
 
         Runtime.getRuntime().gc();
         Runtime.getRuntime().gc();
 
-        assertEquals(1, getTag(o1));
-        assertEquals(2, getTag(o2));
+        assertEquals(1, JniBindings.getTag(o1));
+        assertEquals(2, JniBindings.getTag(o2));
 
         Runtime.getRuntime().gc();
         Runtime.getRuntime().gc();
 
-        setTag(o1, 10);
-        setTag(o2, 20);
+        JniBindings.setTag(o1, 10);
+        JniBindings.setTag(o2, 20);
 
-        assertEquals(10, getTag(o1));
-        assertEquals(20, getTag(o2));
+        assertEquals(10, JniBindings.getTag(o1));
+        assertEquals(20, JniBindings.getTag(o2));
 
         return new WeakReference<Object>(o1);
     }
@@ -93,7 +93,7 @@
             Integer o = new Integer(i);
             l.add(o);
             if (i % 10 != 0) {
-                setTag(o, i % 10);
+                JniBindings.setTag(o, i % 10);
             }
         }
 
@@ -208,6 +208,7 @@
         }
 
         @Override
+        @SuppressWarnings("unchecked")
         public int compareTo(Pair p) {
             if (tag != p.tag) {
                 return Long.compare(tag, p.tag);
@@ -242,11 +243,6 @@
         }
     }
 
-
-    private static native void setTag(Object o, long tag);
-
-    private static native long getTag(Object o);
-
     private static native Object[] getTaggedObjects(long[] searchTags, boolean returnObjects,
             boolean returnTags);
 }