CTS: Add a JVMTI tagging test
Copy ART run-test for basic tagging functionality to CTS. Change
implementation to compare in code instead of text output.
(cherry picked from commit 5358ee6cfd8f22790e90ca46345b76787e00d8a8)
Bug: 32072923
Test: m cts
Test: cts-tradefed run cts-dev --module CtsJvmtiTaggingHostTestCases
Change-Id: I20147b823a8f9e5d288e8e5edc195ab1101375b4
diff --git a/hostsidetests/jvmti/base/jni/Android.mk b/hostsidetests/jvmti/base/jni/Android.mk
index de7428c..295f4c8 100644
--- a/hostsidetests/jvmti/base/jni/Android.mk
+++ b/hostsidetests/jvmti/base/jni/Android.mk
@@ -25,6 +25,9 @@
jni_binder.cpp \
jvmti_helper.cpp \
+# Tagging.
+LOCAL_SRC_FILES += tagging.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
new file mode 100644
index 0000000..7ef996c
--- /dev/null
+++ b/hostsidetests/jvmti/base/jni/tagging.cpp
@@ -0,0 +1,138 @@
+/*
+ * 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 "jni.h"
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "common.h"
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "jvmti.h"
+#include "scoped_primitive_array.h"
+
+namespace cts {
+namespace jvmti {
+namespace tagging {
+
+extern "C" JNIEXPORT void JNICALL Java_android_jvmti_cts_JvmtiTaggingTest_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(
+ JNIEnv* env, jclass klass ATTRIBUTE_UNUSED, jobject obj) {
+ jvmtiEnv* jvmti_env = GetJvmtiEnv();
+ jlong tag = 0;
+ jvmtiError ret = jvmti_env->GetTag(obj, &tag);
+ if (JvmtiErrorToException(env, jvmti_env, ret)) {
+ return 0;
+ }
+ return tag;
+}
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_android_jvmti_cts_JvmtiTaggingTest_getTaggedObjects(
+ JNIEnv* env,
+ jclass klass ATTRIBUTE_UNUSED,
+ jlongArray searchTags,
+ jboolean returnObjects,
+ jboolean returnTags) {
+ jvmtiEnv* jvmti_env = GetJvmtiEnv();
+ ScopedLongArrayRO scoped_array(env);
+ if (searchTags != nullptr) {
+ scoped_array.reset(searchTags);
+ }
+ const jlong* tag_ptr = scoped_array.get();
+ if (tag_ptr == nullptr) {
+ // Can never pass null.
+ tag_ptr = reinterpret_cast<const jlong*>(1);
+ }
+
+ jint result_count = -1;
+ jobject* result_object_array = nullptr;
+ jobject** result_object_array_ptr = returnObjects == JNI_TRUE ? &result_object_array : nullptr;
+ jlong* result_tag_array = nullptr;
+ jlong** result_tag_array_ptr = returnTags == JNI_TRUE ? &result_tag_array : nullptr;
+
+ jvmtiError ret = jvmti_env->GetObjectsWithTags(scoped_array.size(),
+ tag_ptr,
+ &result_count,
+ result_object_array_ptr,
+ result_tag_array_ptr);
+ if (JvmtiErrorToException(env, jvmti_env, ret)) {
+ return nullptr;
+ }
+
+ CHECK_GE(result_count, 0);
+
+ jobjectArray resultObjectArray = nullptr;
+ if (returnObjects == JNI_TRUE) {
+ auto callback = [&](jint i) {
+ return result_object_array[i];
+ };
+ resultObjectArray = CreateObjectArray(env, result_count, "java/lang/Object", callback);
+ if (resultObjectArray == nullptr) {
+ return nullptr;
+ }
+ }
+ if (result_object_array != nullptr) {
+ CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, result_object_array));
+ }
+
+ jlongArray resultTagArray = nullptr;
+ if (returnTags == JNI_TRUE) {
+ resultTagArray = env->NewLongArray(result_count);
+ if (resultTagArray == nullptr) {
+ return nullptr;
+ }
+ env->SetLongArrayRegion(resultTagArray, 0, result_count, result_tag_array);
+ }
+ if (result_tag_array != nullptr) {
+ CheckJvmtiError(jvmti_env, Deallocate(jvmti_env, result_tag_array));
+ }
+
+ jobject count_integer;
+ {
+ ScopedLocalRef<jclass> integer_class(env, env->FindClass("java/lang/Integer"));
+ jmethodID methodID = env->GetMethodID(integer_class.get(), "<init>", "(I)V");
+ count_integer = env->NewObject(integer_class.get(), methodID, result_count);
+ if (count_integer == nullptr) {
+ return nullptr;
+ }
+ }
+
+ auto callback = [&](jint i) -> jobject {
+ switch(i) {
+ case 0:
+ return resultObjectArray;
+ case 1:
+ return resultTagArray;
+ case 2:
+ return count_integer;
+ default:
+ LOG(FATAL) << "Unexpected";
+ return nullptr;
+ }
+ };
+ return CreateObjectArray(env, 3, "java/lang/Object", callback);
+}
+
+} // namespace tagging
+} // namespace jvmti
+} // namespace cts
+
diff --git a/hostsidetests/jvmti/tagging/Android.mk b/hostsidetests/jvmti/tagging/Android.mk
new file mode 100644
index 0000000..61e06a4
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/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 := CtsJvmtiTaggingHostTestCases
+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/tagging/AndroidTest.xml b/hostsidetests/jvmti/tagging/AndroidTest.xml
new file mode 100644
index 0000000..6d76722
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/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="CtsJvmtiTaggingDeviceApp.apk" />
+ <option name="package-name" value="android.jvmti.cts.tagging" />
+ </target_preparer>
+ <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+ <option name="jar" value="CtsJvmtiTaggingHostTestCases.jar" />
+ </test>
+</configuration>
diff --git a/hostsidetests/jvmti/tagging/app/Android.mk b/hostsidetests/jvmti/tagging/app/Android.mk
new file mode 100644
index 0000000..343dc32
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/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 := CtsJvmtiTaggingDeviceApp
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/jvmti/tagging/app/AndroidManifest.xml b/hostsidetests/jvmti/tagging/app/AndroidManifest.xml
new file mode 100755
index 0000000..03509ce
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/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.tagging">
+
+ <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.tagging" >
+ </instrumentation>
+</manifest>
+
diff --git a/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java b/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java
new file mode 100644
index 0000000..d2cfe96
--- /dev/null
+++ b/hostsidetests/jvmti/tagging/app/src/android/jvmti/cts/JvmtiTaggingTest.java
@@ -0,0 +1,252 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Check tagging-related functionality.
+ */
+public class JvmtiTaggingTest extends JvmtiTestBase {
+
+ @Before
+ public void setUp() throws Exception {
+ // Bind our native methods.
+ JniBindings.bindAgentJNI("android/jvmti/cts/JvmtiTaggingTest", getClass().getClassLoader());
+ }
+
+ private static WeakReference<Object> test() {
+ Object o1 = new Object();
+ setTag(o1, 1);
+
+ Object o2 = new Object();
+ setTag(o2, 2);
+
+ assertEquals(1, getTag(o1));
+ assertEquals(2, getTag(o2));
+
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+
+ assertEquals(1, getTag(o1));
+ assertEquals(2, getTag(o2));
+
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+
+ setTag(o1, 10);
+ setTag(o2, 20);
+
+ assertEquals(10, getTag(o1));
+ assertEquals(20, getTag(o2));
+
+ return new WeakReference<Object>(o1);
+ }
+
+ // Very simplistic tagging.
+ @Test
+ public void testTagging() throws Exception {
+ test();
+ }
+
+ @Test
+ public void testTaggingGC() {
+ WeakReference<Object> weak = test();
+
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+
+ if (weak.get() != null) {
+ throw new RuntimeException("WeakReference not cleared");
+ }
+ }
+
+ private ArrayList<Object> l;
+
+ @Test
+ public void testGetTaggedObjects() {
+ // Use an array list to ensure that the objects stay live for a bit. Also gives us a source
+ // to compare to. We use index % 10 as the tag.
+ l = new ArrayList<>();
+
+ for (int i = 0; i < 20; i++) {
+ Integer o = new Integer(i);
+ l.add(o);
+ if (i % 10 != 0) {
+ setTag(o, i % 10);
+ }
+ }
+
+ GetTaggedObjectsExpectation exp1 = new GetTaggedObjectsExpectation(18);
+ getTaggedObjectsRun(null, false, false, exp1);
+
+ GetTaggedObjectsExpectation exp2 = new GetTaggedObjectsExpectation(18);
+ exp2.add(l.get(1), 1).add(l.get(11), 1).add(l.get(2), 2).add(l.get(12), 2).add(l.get(3), 3)
+ .add(l.get(13), 3).add(l.get(4), 4).add(l.get(14), 4).add(l.get(5), 5)
+ .add(l.get(15), 5).add(l.get(6), 6).add(l.get(16), 6).add(l.get(7), 7)
+ .add(l.get(17), 7).add(l.get(8), 8).add(l.get(18), 8).add(l.get(9), 9)
+ .add(l.get(19), 9);
+ getTaggedObjectsRun(null, true, true, exp2);
+
+ GetTaggedObjectsExpectation exp3 = new GetTaggedObjectsExpectation(4);
+ exp3.add(l.get(2), 2).add(l.get(12), 2).add(l.get(5), 5).add(l.get(15), 5);
+ getTaggedObjectsRun(new long[] {2, 5}, true, true, exp3);
+
+ GetTaggedObjectsExpectation exp4 = new GetTaggedObjectsExpectation(18);
+ exp4.add(null, 1).add(null, 1).add(null, 2).add(null, 2).add(null, 3).add(null, 3)
+ .add(null, 4).add(null, 4).add(null, 5).add(null, 5).add(null, 6).add(null, 6)
+ .add(null, 7).add(null, 7).add(null, 8).add(null, 8).add(null, 9).add(null, 9);
+ getTaggedObjectsRun(null, false, true, exp4);
+
+ GetTaggedObjectsExpectation exp5 = new GetTaggedObjectsExpectation(18);
+ for (int i = 0; i < l.size(); i++) {
+ if (i % 10 != 0) {
+ exp5.add(l.get(i), 0);
+ }
+ }
+ getTaggedObjectsRun(null, true, false, exp5);
+
+ l = null;
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().gc();
+ }
+
+ private static void getTaggedObjectsRun(long[] searchTags, boolean returnObjects,
+ boolean returnTags, GetTaggedObjectsExpectation exp) {
+ Object[] result = getTaggedObjects(searchTags, returnObjects, returnTags);
+
+ Object[] objects = (Object[]) result[0];
+ long[] tags = (long[]) result[1];
+ int count = (int) result[2];
+
+ exp.check(count, objects, tags);
+ }
+
+ private static class GetTaggedObjectsExpectation {
+ List<Pair> expectations = new LinkedList<>();
+ int count;
+
+ public GetTaggedObjectsExpectation(int c) {
+ count = c;
+ }
+
+ public void check(int count, Object[] objects, long[] tags) {
+ assertEquals(this.count, count);
+
+ if (objects == null && tags == null) {
+ assertTrue(expectations.isEmpty());
+ return;
+ }
+
+ int l1 = objects == null ? 0 : objects.length;
+ int l2 = tags == null ? 0 : tags.length;
+ int l = Math.max(l1, l2);
+ List<Pair> tmp = new ArrayList<>(l);
+ for (int i = 0; i < l; i++) {
+ tmp.add(new Pair(objects == null ? null : objects[i], tags == null ? 0 : tags[i]));
+ }
+ Collections.sort(tmp);
+ Collections.sort(expectations);
+
+ if (!expectations.equals(tmp)) {
+ for (int i = 0; i < expectations.size(); i++) {
+ Pair p1 = expectations.get(i);
+ Pair p2 = tmp.get(i);
+ if (!p1.equals(p2)) {
+ String s = "Not equal: " + p1 + "[" + System.identityHashCode(p1.obj) +
+ "] vs " + p2 + "[" + System.identityHashCode(p2.obj);
+ throw new RuntimeException(s);
+ }
+ }
+ }
+
+ assertEquals(expectations, tmp);
+ }
+
+ public GetTaggedObjectsExpectation add(Object o, long l) {
+ expectations.add(new Pair(o, l));
+ return this;
+ }
+ }
+
+ private static class Pair implements Comparable<Pair> {
+ Object obj;
+ long tag;
+
+ public Pair(Object o, long t) {
+ obj = o;
+ tag = t;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof Pair) {
+ Pair p = (Pair)obj;
+ return tag == p.tag && (p.obj == null ? this.obj == null : p.obj.equals(this.obj));
+ }
+ return false;
+ }
+
+ @Override
+ public int compareTo(Pair p) {
+ if (tag != p.tag) {
+ return Long.compare(tag, p.tag);
+ }
+
+ if ((obj instanceof Comparable) && (p.obj instanceof Comparable)) {
+ // It's not really correct, but w/e, best effort.
+ int result = ((Comparable<Object>) obj).compareTo(p.obj);
+ if (result != 0) {
+ return result;
+ }
+ }
+
+ if (obj != null && p.obj != null) {
+ return obj.hashCode() - p.obj.hashCode();
+ }
+
+ if (obj != null) {
+ return 1;
+ }
+
+ if (p.obj != null) {
+ return -1;
+ }
+
+ return hashCode() - p.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "<" + obj + ";" + tag + ">";
+ }
+ }
+
+
+ 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);
+}