ART: Refactor run-test 912 (1/3)

Refactor the test for CTS. Break out the ART-specific part. Move
the rest into the regular layout. Move class events to their own
thread, and report them on the Java side. Use InMemoryDexClassLoader
for classloaders to avoid explicit files.

Bug: 32072923
Test: none
Change-Id: I80e27cc9c21646bbecd1de1a246f583a0d388e05
diff --git a/test/912-classes/classes.cc b/test/912-classes/classes.cc
index 2636367..869eacd 100644
--- a/test/912-classes/classes.cc
+++ b/test/912-classes/classes.cc
@@ -16,50 +16,39 @@
 
 #include <stdio.h>
 
-#include "android-base/macros.h"
+#include <mutex>
+#include <vector>
 
-#include "class_linker.h"
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+
 #include "jni.h"
-#include "mirror/class_loader.h"
 #include "jvmti.h"
-#include "runtime.h"
-#include "scoped_local_ref.h"
-#include "scoped_utf_chars.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread-inl.h"
 
 // Test infrastructure
 #include "jni_helper.h"
 #include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
 #include "test_env.h"
 
 namespace art {
 namespace Test912Classes {
 
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_isModifiableClass(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isModifiableClass(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jboolean res = JNI_FALSE;
   jvmtiError result = jvmti_env->IsModifiableClass(klass, &res);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running IsModifiableClass: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-    return JNI_FALSE;
-  }
+  JvmtiErrorToException(env, jvmti_env, result);
   return res;
 }
 
-extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassSignature(
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassSignature(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   char* sig;
   char* gen;
   jvmtiError result = jvmti_env->GetClassSignature(klass, &sig, &gen);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running GetClassSignature: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+  if (JvmtiErrorToException(env, jvmti_env, result)) {
     return nullptr;
   }
 
@@ -83,57 +72,36 @@
   return ret;
 }
 
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterface(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isInterface(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jboolean is_interface = JNI_FALSE;
   jvmtiError result = jvmti_env->IsInterface(klass, &is_interface);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running IsInterface: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-    return JNI_FALSE;
-  }
+  JvmtiErrorToException(env, jvmti_env, result);
   return is_interface;
 }
 
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_isArrayClass(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912_isArrayClass(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jboolean is_array_class = JNI_FALSE;
   jvmtiError result = jvmti_env->IsArrayClass(klass, &is_array_class);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running IsArrayClass: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-    return JNI_FALSE;
-  }
+  JvmtiErrorToException(env, jvmti_env, result);
   return is_array_class;
 }
 
-extern "C" JNIEXPORT jint JNICALL Java_Main_getClassModifiers(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+extern "C" JNIEXPORT jint JNICALL Java_art_Test912_getClassModifiers(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jint mod;
   jvmtiError result = jvmti_env->GetClassModifiers(klass, &mod);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running GetClassModifiers: %s\n", err);
-    return JNI_FALSE;
-  }
+  JvmtiErrorToException(env, jvmti_env, result);
   return mod;
 }
 
-extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassFields(
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassFields(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jint count = 0;
   jfieldID* fields = nullptr;
   jvmtiError result = jvmti_env->GetClassFields(klass, &count, &fields);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running GetClassFields: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
+  if (JvmtiErrorToException(env, jvmti_env, result)) {
     return nullptr;
   }
 
@@ -153,15 +121,12 @@
   return ret;
 }
 
-extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassMethods(
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassMethods(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jint count = 0;
   jmethodID* methods = nullptr;
   jvmtiError result = jvmti_env->GetClassMethods(klass, &count, &methods);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running GetClassMethods: %s\n", err);
+  if (JvmtiErrorToException(env, jvmti_env, result)) {
     return nullptr;
   }
 
@@ -181,15 +146,12 @@
   return ret;
 }
 
-extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getImplementedInterfaces(
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getImplementedInterfaces(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jint count = 0;
   jclass* classes = nullptr;
   jvmtiError result = jvmti_env->GetImplementedInterfaces(klass, &count, &classes);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running GetImplementedInterfaces: %s\n", err);
+  if (JvmtiErrorToException(env, jvmti_env, result)) {
     return nullptr;
   }
 
@@ -203,35 +165,23 @@
   return ret;
 }
 
-extern "C" JNIEXPORT jint JNICALL Java_Main_getClassStatus(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+extern "C" JNIEXPORT jint JNICALL Java_art_Test912_getClassStatus(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jint status;
   jvmtiError result = jvmti_env->GetClassStatus(klass, &status);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running GetClassStatus: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-    return JNI_FALSE;
-  }
+  JvmtiErrorToException(env, jvmti_env, result);
   return status;
 }
 
-extern "C" JNIEXPORT jobject JNICALL Java_Main_getClassLoader(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
+extern "C" JNIEXPORT jobject JNICALL Java_art_Test912_getClassLoader(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jobject classloader;
   jvmtiError result = jvmti_env->GetClassLoader(klass, &classloader);
-  if (result != JVMTI_ERROR_NONE) {
-    char* err;
-    jvmti_env->GetErrorName(result, &err);
-    printf("Failure running GetClassLoader: %s\n", err);
-    jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(err));
-    return nullptr;
-  }
+  JvmtiErrorToException(env, jvmti_env, result);
   return classloader;
 }
 
-extern "C" JNIEXPORT jobjectArray JNICALL Java_Main_getClassLoaderClasses(
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassLoaderClasses(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jobject jclassloader) {
   jint count = 0;
   jclass* classes = nullptr;
@@ -250,7 +200,7 @@
   return ret;
 }
 
-extern "C" JNIEXPORT jintArray JNICALL Java_Main_getClassVersion(
+extern "C" JNIEXPORT jintArray JNICALL Java_art_Test912_getClassVersion(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   jint major, minor;
   jvmtiError result = jvmti_env->GetClassVersionNumbers(klass, &minor, &major);
@@ -325,6 +275,22 @@
   JvmtiErrorToException(env, jvmti_env, ret);
 }
 
+static std::mutex gEventsMutex;
+static std::vector<std::string> gEvents;
+
+extern "C" JNIEXPORT jobjectArray JNICALL Java_art_Test912_getClassLoadMessages(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED) {
+  std::lock_guard<std::mutex> guard(gEventsMutex);
+  jobjectArray ret = CreateObjectArray(env,
+                                       static_cast<jint>(gEvents.size()),
+                                       "java/lang/String",
+                                       [&](jint i) {
+    return env->NewStringUTF(gEvents[i].c_str());
+  });
+  gEvents.clear();
+  return ret;
+}
+
 class ClassLoadPreparePrinter {
  public:
   static void JNICALL ClassLoadCallback(jvmtiEnv* jenv,
@@ -339,7 +305,14 @@
     if (thread_name == "") {
       return;
     }
-    printf("Load: %s on %s\n", name.c_str(), thread_name.c_str());
+    if (thread_name_filter_ != "" && thread_name_filter_ != thread_name) {
+      return;
+    }
+
+    std::lock_guard<std::mutex> guard(gEventsMutex);
+    gEvents.push_back(android::base::StringPrintf("Load: %s on %s",
+                                                  name.c_str(),
+                                                  thread_name.c_str()));
   }
 
   static void JNICALL ClassPrepareCallback(jvmtiEnv* jenv,
@@ -354,14 +327,18 @@
     if (thread_name == "") {
       return;
     }
-    std::string cur_thread_name = GetThreadName(Thread::Current());
-    printf("Prepare: %s on %s (cur=%s)\n",
-           name.c_str(),
-           thread_name.c_str(),
-           cur_thread_name.c_str());
+    if (thread_name_filter_ != "" && thread_name_filter_ != thread_name) {
+      return;
+    }
+    std::string cur_thread_name = GetThreadName(jenv, jni_env, nullptr);
+
+    std::lock_guard<std::mutex> guard(gEventsMutex);
+    gEvents.push_back(android::base::StringPrintf("Prepare: %s on %s (cur=%s)",
+                                                  name.c_str(),
+                                                  thread_name.c_str(),
+                                                  cur_thread_name.c_str()));
   }
 
- private:
   static std::string GetThreadName(jvmtiEnv* jenv, JNIEnv* jni_env, jthread thread) {
     jvmtiThreadInfo info;
     jvmtiError result = jenv->GetThreadInfo(thread, &info);
@@ -382,60 +359,28 @@
     return tmp;
   }
 
-  static std::string GetThreadName(Thread* thread) {
-    std::string tmp;
-    thread->GetThreadName(tmp);
-    return tmp;
-  }
+  static std::string thread_name_filter_;
 };
+std::string ClassLoadPreparePrinter::thread_name_filter_;
 
-extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadPreparePrintEvents(
-    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean enable) {
+extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPreparePrintEvents(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean enable, jthread thread) {
+  if (thread != nullptr) {
+    ClassLoadPreparePrinter::thread_name_filter_ =
+        ClassLoadPreparePrinter::GetThreadName(jvmti_env, env, thread);
+  } else {
+    ClassLoadPreparePrinter::thread_name_filter_ = "";
+  }
+
   EnableEvents(env,
                enable,
                ClassLoadPreparePrinter::ClassLoadCallback,
                ClassLoadPreparePrinter::ClassPrepareCallback);
 }
 
-struct ClassLoadSeen {
-  static void JNICALL ClassLoadSeenCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED,
-                                            JNIEnv* jni_env ATTRIBUTE_UNUSED,
-                                            jthread thread ATTRIBUTE_UNUSED,
-                                            jclass klass ATTRIBUTE_UNUSED) {
-    saw_event = true;
-  }
-
-  static bool saw_event;
-};
-bool ClassLoadSeen::saw_event = false;
-
-extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadSeenEvents(
-    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
-  EnableEvents(env, b, ClassLoadSeen::ClassLoadSeenCallback, nullptr);
-}
-
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_hadLoadEvent(
-    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED) {
-  return ClassLoadSeen::saw_event ? JNI_TRUE : JNI_FALSE;
-}
-
-extern "C" JNIEXPORT jboolean JNICALL Java_Main_isLoadedClass(
-    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring class_name) {
-  ScopedUtfChars name(env, class_name);
-  ScopedObjectAccess soa(Thread::Current());
-  Runtime* current = Runtime::Current();
-  ClassLinker* class_linker = current->GetClassLinker();
-  bool found =
-      class_linker->LookupClass(
-          soa.Self(),
-          name.c_str(),
-          soa.Decode<mirror::ClassLoader>(current->GetSystemClassLoader())) != nullptr;
-  return found ? JNI_TRUE : JNI_FALSE;
-}
-
 class ClassLoadPrepareEquality {
  public:
-  static constexpr const char* kClassName = "LMain$ClassE;";
+  static constexpr const char* kClassName = "Lart/Test912$ClassE;";
   static constexpr const char* kStorageFieldName = "STATIC";
   static constexpr const char* kStorageFieldSig = "Ljava/lang/Object;";
   static constexpr const char* kStorageWeakFieldName = "WEAK";
@@ -553,13 +498,13 @@
 bool ClassLoadPrepareEquality::found_ = false;
 bool ClassLoadPrepareEquality::compared_ = false;
 
-extern "C" JNIEXPORT void JNICALL Java_Main_setEqualityEventStorageClass(
+extern "C" JNIEXPORT void JNICALL Java_art_Test912_setEqualityEventStorageClass(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jclass klass) {
   ClassLoadPrepareEquality::storage_class_ =
       reinterpret_cast<jclass>(env->NewGlobalRef(klass));
 }
 
-extern "C" JNIEXPORT void JNICALL Java_Main_enableClassLoadPrepareEqualityEvents(
+extern "C" JNIEXPORT void JNICALL Java_art_Test912_enableClassLoadPrepareEqualityEvents(
     JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
   EnableEvents(env,
                b,
diff --git a/test/912-classes/classes_art.cc b/test/912-classes/classes_art.cc
new file mode 100644
index 0000000..de2e456
--- /dev/null
+++ b/test/912-classes/classes_art.cc
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2013 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 <stdio.h>
+
+#include <mutex>
+#include <vector>
+
+#include "android-base/macros.h"
+#include "android-base/stringprintf.h"
+
+#include "jni.h"
+#include "jvmti.h"
+
+// Test infrastructure
+#include "jni_helper.h"
+#include "jvmti_helper.h"
+#include "scoped_local_ref.h"
+#include "scoped_utf_chars.h"
+#include "test_env.h"
+
+namespace art {
+namespace Test912ArtClasses {
+
+static void EnableEvents(JNIEnv* env,
+                         jboolean enable,
+                         decltype(jvmtiEventCallbacks().ClassLoad) class_load,
+                         decltype(jvmtiEventCallbacks().ClassPrepare) class_prepare) {
+  if (enable == JNI_FALSE) {
+    jvmtiError ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                                         JVMTI_EVENT_CLASS_LOAD,
+                                                         nullptr);
+    if (JvmtiErrorToException(env, jvmti_env, ret)) {
+      return;
+    }
+    ret = jvmti_env->SetEventNotificationMode(JVMTI_DISABLE,
+                                              JVMTI_EVENT_CLASS_PREPARE,
+                                              nullptr);
+    JvmtiErrorToException(env, jvmti_env, ret);
+    return;
+  }
+
+  jvmtiEventCallbacks callbacks;
+  memset(&callbacks, 0, sizeof(jvmtiEventCallbacks));
+  callbacks.ClassLoad = class_load;
+  callbacks.ClassPrepare = class_prepare;
+  jvmtiError ret = jvmti_env->SetEventCallbacks(&callbacks, sizeof(callbacks));
+  if (JvmtiErrorToException(env, jvmti_env, ret)) {
+    return;
+  }
+
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_CLASS_LOAD,
+                                            nullptr);
+  if (JvmtiErrorToException(env, jvmti_env, ret)) {
+    return;
+  }
+  ret = jvmti_env->SetEventNotificationMode(JVMTI_ENABLE,
+                                            JVMTI_EVENT_CLASS_PREPARE,
+                                            nullptr);
+  JvmtiErrorToException(env, jvmti_env, ret);
+}
+
+struct ClassLoadSeen {
+  static void JNICALL ClassLoadSeenCallback(jvmtiEnv* jenv ATTRIBUTE_UNUSED,
+                                            JNIEnv* jni_env ATTRIBUTE_UNUSED,
+                                            jthread thread ATTRIBUTE_UNUSED,
+                                            jclass klass ATTRIBUTE_UNUSED) {
+    saw_event = true;
+  }
+
+  static bool saw_event;
+};
+bool ClassLoadSeen::saw_event = false;
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test912Art_enableClassLoadSeenEvents(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jboolean b) {
+  EnableEvents(env, b, ClassLoadSeen::ClassLoadSeenCallback, nullptr);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912Art_hadLoadEvent(
+    JNIEnv* env ATTRIBUTE_UNUSED, jclass Main_klass ATTRIBUTE_UNUSED) {
+  return ClassLoadSeen::saw_event ? JNI_TRUE : JNI_FALSE;
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912Art_isLoadedClass(
+    JNIEnv* env, jclass Main_klass ATTRIBUTE_UNUSED, jstring class_name) {
+  ScopedUtfChars name(env, class_name);
+
+  jint class_count;
+  jclass* classes;
+  jvmtiError res = jvmti_env->GetLoadedClasses(&class_count, &classes);
+  if (JvmtiErrorToException(env, jvmti_env, res)) {
+    return JNI_FALSE;
+  }
+
+  bool found = false;
+  for (jint i = 0; !found && i < class_count; ++i) {
+    char* sig;
+    jvmtiError res2 = jvmti_env->GetClassSignature(classes[i], &sig, nullptr);
+    if (JvmtiErrorToException(env, jvmti_env, res2)) {
+      return JNI_FALSE;
+    }
+
+    found = strcmp(name.c_str(), sig) == 0;
+
+    CheckJvmtiError(jvmti_env, jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(sig)));
+  }
+
+  CheckJvmtiError(jvmti_env, jvmti_env->Deallocate(reinterpret_cast<unsigned char*>(classes)));
+
+  return found;
+}
+
+// We use the implementations from runtime_state.cc.
+
+extern "C" JNIEXPORT void JNICALL Java_Main_ensureJitCompiled(JNIEnv* env,
+                                                             jclass,
+                                                             jclass cls,
+                                                             jstring method_name);
+extern "C" JNIEXPORT jboolean JNICALL Java_Main_hasJit(JNIEnv*, jclass);
+
+extern "C" JNIEXPORT void JNICALL Java_art_Test912Art_ensureJitCompiled(
+    JNIEnv* env, jclass klass, jclass test_class, jstring name) {
+  Java_Main_ensureJitCompiled(env, klass, test_class, name);
+}
+
+extern "C" JNIEXPORT jboolean JNICALL Java_art_Test912Art_hasJit(JNIEnv* env, jclass klass) {
+  return Java_Main_hasJit(env, klass);
+}
+
+}  // namespace Test912ArtClasses
+}  // namespace art
diff --git a/test/912-classes/expected.txt b/test/912-classes/expected.txt
index 0f2920a..9dcc5f9 100644
--- a/test/912-classes/expected.txt
+++ b/test/912-classes/expected.txt
@@ -6,14 +6,14 @@
 11
 [Ljava/util/List;, <E:Ljava/lang/Object;>Ljava/lang/Object;Ljava/util/Collection<TE;>;]
 601
-[L$Proxy0;, null]
+[L$Proxy20;, null]
 11
 [I, null]
 411
 [[D, null]
 411
 int interface=false array=false modifiable=false
-$Proxy0 interface=false array=false modifiable=false
+$Proxy20 interface=false array=false modifiable=false
 java.lang.Runnable interface=true array=false modifiable=false
 java.lang.String interface=false array=false modifiable=false
 java.util.ArrayList interface=false array=false modifiable=true
@@ -29,70 +29,65 @@
 int 100000
 class [Ljava.lang.String; 10000
 class java.lang.Object 111
-class Main$TestForNonInit 11
-class Main$TestForInitFail 1011
+class art.Test912$TestForNonInit 11
+class art.Test912$TestForInitFail 1011
 int []
 class [Ljava.lang.String; []
 class java.lang.Object []
-interface Main$InfA []
-interface Main$InfB [interface Main$InfA]
-interface Main$InfC [interface Main$InfB]
-class Main$ClassA [interface Main$InfA]
-class Main$ClassB [interface Main$InfB]
-class Main$ClassC [interface Main$InfA, interface Main$InfC]
+interface art.Test912$InfA []
+interface art.Test912$InfB [interface art.Test912$InfA]
+interface art.Test912$InfC [interface art.Test912$InfB]
+class art.Test912$ClassA [interface art.Test912$InfA]
+class art.Test912$ClassB [interface art.Test912$InfB]
+class art.Test912$ClassC [interface art.Test912$InfA, interface art.Test912$InfC]
 class java.lang.String null
 class [Ljava.lang.String; null
-interface Main$InfA dalvik.system.PathClassLoader
-class $Proxy0 dalvik.system.PathClassLoader
+interface art.Test912$InfA dalvik.system.PathClassLoader
+class $Proxy20 dalvik.system.PathClassLoader
 
-boot <- src <- src-ex (A,B)
-912-classes-ex.jar+ -> 912-classes.jar+ -> 
+boot <- (B) <- (A,C)
 [class A, class B, class java.lang.Object]
-912-classes.jar+ -> 
 [class B, class java.lang.Object]
 
-boot <- src (B) <- src-ex (A, List)
-912-classes-ex.jar+ -> 912-classes.jar+ -> 
+boot <- (B) <- (A, List)
 [class A, class java.lang.Object, interface java.util.List]
-912-classes.jar+ -> 
 [class B, class java.lang.Object]
 
-boot <- src+src-ex (A,B)
-912-classes.jar+ -> 
+boot <- 1+2 (A,B)
 [class A, class B, class java.lang.Object]
 
 [37, 0]
 
 B, false
-Load: LB; on main
-Prepare: LB; on main (cur=main)
+Load: LB; on ClassEvents
+Prepare: LB; on ClassEvents (cur=ClassEvents)
 B, true
-Load: LB; on main
-Prepare: LB; on main (cur=main)
+Load: LB; on ClassEvents
+Prepare: LB; on ClassEvents (cur=ClassEvents)
 C, false
-Load: LA; on main
-Prepare: LA; on main (cur=main)
-Load: LC; on main
-Prepare: LC; on main (cur=main)
+Load: LA; on ClassEvents
+Prepare: LA; on ClassEvents (cur=ClassEvents)
+Load: LC; on ClassEvents
+Prepare: LC; on ClassEvents (cur=ClassEvents)
 A, false
 C, true
-Load: LA; on main
-Prepare: LA; on main (cur=main)
-Load: LC; on main
-Prepare: LC; on main (cur=main)
+Load: LA; on ClassEvents
+Prepare: LA; on ClassEvents (cur=ClassEvents)
+Load: LC; on ClassEvents
+Prepare: LC; on ClassEvents (cur=ClassEvents)
 A, true
 A, true
-Load: LA; on main
-Prepare: LA; on main (cur=main)
+Load: LA; on ClassEvents
+Prepare: LA; on ClassEvents (cur=ClassEvents)
 C, true
-Load: LC; on main
-Prepare: LC; on main (cur=main)
+Load: LC; on ClassEvents
+Prepare: LC; on ClassEvents (cur=ClassEvents)
 C, true
 Load: LA; on TestRunner
 Prepare: LA; on TestRunner (cur=TestRunner)
 Load: LC; on TestRunner
 Prepare: LC; on TestRunner (cur=TestRunner)
-Load: L$Proxy1; on main
-Prepare: L$Proxy1; on main (cur=main)
-Load: [LMain; on main
-Prepare: [LMain; on main (cur=main)
+Load: L$Proxy21; on ClassEvents
+Prepare: L$Proxy21; on ClassEvents (cur=ClassEvents)
+Load: [Lart/Test912; on ClassEvents
+Prepare: [Lart/Test912; on ClassEvents (cur=ClassEvents)
diff --git a/test/912-classes/src-ex/A.java b/test/912-classes/src-ex/A.java
deleted file mode 100644
index 2c43cfb..0000000
--- a/test/912-classes/src-ex/A.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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.
- */
-
-public class A {
-}
diff --git a/test/912-classes/src-ex/C.java b/test/912-classes/src-ex/C.java
deleted file mode 100644
index 97f8021..0000000
--- a/test/912-classes/src-ex/C.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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.
- */
-
-public class C extends A {
-}
diff --git a/test/912-classes/src/B.java b/test/912-classes/src/B.java
deleted file mode 100644
index 52ce4dd..0000000
--- a/test/912-classes/src/B.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * 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.
- */
-
-public class B {
-}
diff --git a/test/912-classes/src/Main.java b/test/912-classes/src/Main.java
index 6c8858a..f3ff2b0 100644
--- a/test/912-classes/src/Main.java
+++ b/test/912-classes/src/Main.java
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+package art;
+
 import java.lang.ref.Reference;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Proxy;
@@ -21,9 +23,9 @@
 import java.util.Arrays;
 import java.util.Comparator;
 
-public class Main {
-  public static void main(String[] args) throws Exception {
-    art.Main.bindAgentJNIForClass(Main.class);
+public class Test912 {
+  public static void run() throws Exception {
+    art.Main.bindAgentJNIForClass(Test912.class);
     doTest();
   }
 
@@ -89,18 +91,19 @@
 
     System.out.println();
 
-    testClassEvents();
-  }
-
-  private static Class<?> proxyClass = null;
-
-  private static Class<?> getProxyClass() throws Exception {
-    if (proxyClass != null) {
-      return proxyClass;
-    }
-
-    proxyClass = Proxy.getProxyClass(Main.class.getClassLoader(), new Class[] { Runnable.class });
-    return proxyClass;
+    // Use a dedicated thread to have a well-defined current thread.
+    Thread classEventsThread = new Thread("ClassEvents") {
+      @Override
+      public void run() {
+        try {
+          testClassEvents();
+        } catch (Exception e) {
+          throw new RuntimeException(e);
+        }
+      }
+    };
+    classEventsThread.start();
+    classEventsThread.join();
   }
 
   private static void testClass(String className) throws Exception {
@@ -166,34 +169,34 @@
   }
 
   private static void testClassLoaderClasses() throws Exception {
-    ClassLoader boot = ClassLoader.getSystemClassLoader().getParent();
-    while (boot.getParent() != null) {
-      boot = boot.getParent();
-    }
-
     System.out.println();
-    System.out.println("boot <- src <- src-ex (A,B)");
-    ClassLoader cl1 = create(create(boot, DEX1), DEX2);
+    System.out.println("boot <- (B) <- (A,C)");
+    ClassLoader cl1 = DexData.create2(DexData.create1());
     Class.forName("B", false, cl1);
     Class.forName("A", false, cl1);
     printClassLoaderClasses(cl1);
 
     System.out.println();
-    System.out.println("boot <- src (B) <- src-ex (A, List)");
-    ClassLoader cl2 = create(create(boot, DEX1), DEX2);
+    System.out.println("boot <- (B) <- (A, List)");
+    ClassLoader cl2 = DexData.create2(DexData.create1());
     Class.forName("A", false, cl2);
     Class.forName("java.util.List", false, cl2);
     Class.forName("B", false, cl2.getParent());
     printClassLoaderClasses(cl2);
 
     System.out.println();
-    System.out.println("boot <- src+src-ex (A,B)");
-    ClassLoader cl3 = create(boot, DEX1, DEX2);
+    System.out.println("boot <- 1+2 (A,B)");
+    ClassLoader cl3 = DexData.create12();
     Class.forName("B", false, cl3);
     Class.forName("A", false, cl3);
     printClassLoaderClasses(cl3);
 
     // Check that the boot classloader dumps something non-empty.
+    ClassLoader boot = ClassLoader.getSystemClassLoader().getParent();
+    while (boot.getParent() != null) {
+      boot = boot.getParent();
+    }
+
     Class<?>[] bootClasses = getClassLoaderClasses(boot);
     if (bootClasses.length == 0) {
       throw new RuntimeException("No classes initiated by boot classloader.");
@@ -236,9 +239,10 @@
       @Override
       public void run() {
         try {
-          ClassLoader cl6 = create(boot, DEX1, DEX2);
+          ClassLoader cl6 = DexData.create12();
           System.out.println("C, true");
           Class.forName("C", true, cl6);
+          printClassLoadMessages();
         } catch (Exception e) {
           throw new RuntimeException(e);
         }
@@ -249,85 +253,68 @@
     dummyThread.start();
     dummyThread.join();
 
-    ensureJitCompiled(Main.class, "testClassEvents");
+    enableClassLoadPreparePrintEvents(true, Thread.currentThread());
 
-    enableClassLoadPreparePrintEvents(true);
-
-    ClassLoader cl1 = create(boot, DEX1, DEX2);
+    ClassLoader cl1 = DexData.create12();
     System.out.println("B, false");
     Class.forName("B", false, cl1);
+    printClassLoadMessages();
 
-    ClassLoader cl2 = create(boot, DEX1, DEX2);
+    ClassLoader cl2 = DexData.create12();
     System.out.println("B, true");
     Class.forName("B", true, cl2);
+    printClassLoadMessages();
 
-    ClassLoader cl3 = create(boot, DEX1, DEX2);
+    ClassLoader cl3 = DexData.create12();
     System.out.println("C, false");
     Class.forName("C", false, cl3);
+    printClassLoadMessages();
     System.out.println("A, false");
     Class.forName("A", false, cl3);
+    printClassLoadMessages();
 
-    ClassLoader cl4 = create(boot, DEX1, DEX2);
+    ClassLoader cl4 = DexData.create12();
     System.out.println("C, true");
     Class.forName("C", true, cl4);
+    printClassLoadMessages();
     System.out.println("A, true");
     Class.forName("A", true, cl4);
+    printClassLoadMessages();
 
-    ClassLoader cl5 = create(boot, DEX1, DEX2);
+    ClassLoader cl5 = DexData.create12();
     System.out.println("A, true");
     Class.forName("A", true, cl5);
+    printClassLoadMessages();
     System.out.println("C, true");
     Class.forName("C", true, cl5);
+    printClassLoadMessages();
+
+    enableClassLoadPreparePrintEvents(false, null);
 
     Thread t = new Thread(r, "TestRunner");
+    enableClassLoadPreparePrintEvents(true, t);
     t.start();
     t.join();
+    enableClassLoadPreparePrintEvents(false, null);
+
+    enableClassLoadPreparePrintEvents(true, Thread.currentThread());
 
     // Check creation of arrays and proxies.
-    Proxy.getProxyClass(Main.class.getClassLoader(), new Class[] { Comparable.class });
-    Class.forName("[LMain;");
+    Proxy.getProxyClass(Main.class.getClassLoader(), new Class[] { Comparable.class, I0.class });
+    Class.forName("[Lart.Test912;");
+    printClassLoadMessages();
 
-    enableClassLoadPreparePrintEvents(false);
-
-    // Note: the JIT part of this test is about the JIT pulling in a class not yet touched by
-    //       anything else in the system. This could be the verifier or the interpreter. We
-    //       block the interpreter by calling ensureJitCompiled. The verifier, however, must
-    //       run in configurations where dex2oat didn't verify the class itself. So explicitly
-    //       check whether the class has been already loaded, and skip then.
-    // TODO: Add multiple configurations to the run script once that becomes easier to do.
-    if (hasJit() && !isLoadedClass("Main$ClassD")) {
-      testClassEventsJit();
-    }
+    enableClassLoadPreparePrintEvents(false, null);
 
     testClassLoadPrepareEquality();
   }
 
-  private static void testClassEventsJit() throws Exception {
-    enableClassLoadSeenEvents(true);
-
-    testClassEventsJitImpl();
-
-    enableClassLoadSeenEvents(false);
-
-    if (!hadLoadEvent()) {
-      throw new RuntimeException("Did not get expected load event.");
-    }
-  }
-
-  private static void testClassEventsJitImpl() throws Exception {
-    ensureJitCompiled(Main.class, "testClassEventsJitImpl");
-
-    if (ClassD.x != 1) {
-      throw new RuntimeException("Unexpected value");
-    }
-  }
-
   private static void testClassLoadPrepareEquality() throws Exception {
     setEqualityEventStorageClass(ClassF.class);
 
     enableClassLoadPrepareEqualityEvents(true);
 
-    Class.forName("Main$ClassE");
+    Class.forName("art.Test912$ClassE");
 
     enableClassLoadPrepareEqualityEvents(false);
   }
@@ -338,39 +325,17 @@
         break;
       }
 
-      ClassLoader saved = cl;
-      for (;;) {
-        if (cl == null || !cl.getClass().getName().startsWith("dalvik.system")) {
-          break;
-        }
-        String s = cl.toString();
-        int index1 = s.indexOf("zip file");
-        int index2 = s.indexOf(']', index1);
-        if (index2 < 0) {
-          throw new RuntimeException("Unexpected classloader " + s);
-        }
-        String zip_file = s.substring(index1, index2);
-        int index3 = zip_file.indexOf('"');
-        int index4 = zip_file.indexOf('"', index3 + 1);
-        if (index4 < 0) {
-          throw new RuntimeException("Unexpected classloader " + s);
-        }
-        String paths = zip_file.substring(index3 + 1, index4);
-        String pathArray[] = paths.split(":");
-        for (String path : pathArray) {
-          int index5 = path.lastIndexOf('/');
-          System.out.print(path.substring(index5 + 1));
-          System.out.print('+');
-        }
-        System.out.print(" -> ");
-        cl = cl.getParent();
-      }
-      System.out.println();
-      Class<?> classes[] = getClassLoaderClasses(saved);
+      Class<?> classes[] = getClassLoaderClasses(cl);
       Arrays.sort(classes, new ClassNameComparator());
       System.out.println(Arrays.toString(classes));
 
-      cl = saved.getParent();
+      cl = cl.getParent();
+    }
+  }
+
+  private static void printClassLoadMessages() {
+    for (String s : getClassLoadMessages()) {
+      System.out.println(s);
     }
   }
 
@@ -394,14 +359,8 @@
 
   private static native int[] getClassVersion(Class<?> c);
 
-  private static native void enableClassLoadPreparePrintEvents(boolean b);
-
-  private static native void ensureJitCompiled(Class<?> c, String name);
-
-  private static native boolean hasJit();
-  private static native boolean isLoadedClass(String name);
-  private static native void enableClassLoadSeenEvents(boolean b);
-  private static native boolean hadLoadEvent();
+  private static native void enableClassLoadPreparePrintEvents(boolean b, Thread filter);
+  private static native String[] getClassLoadMessages();
 
   private static native void setEqualityEventStorageClass(Class<?> c);
   private static native void enableClassLoadPrepareEqualityEvents(boolean b);
@@ -428,10 +387,6 @@
   public abstract static class ClassC implements InfA, InfC {
   }
 
-  public static class ClassD {
-    static int x = 1;
-  }
-
   public static class ClassE {
     public void foo() {
     }
@@ -444,22 +399,56 @@
     public static Reference<Object> WEAK = null;
   }
 
-  private static final String DEX1 = System.getenv("DEX_LOCATION") + "/912-classes.jar";
-  private static final String DEX2 = System.getenv("DEX_LOCATION") + "/912-classes-ex.jar";
-
-  private static ClassLoader create(ClassLoader parent, String... elements) throws Exception {
-    // Note: We use a PathClassLoader, as we do not care about code performance. We only load
-    //       the classes, and they're empty.
-    Class<?> pathClassLoaderClass = Class.forName("dalvik.system.PathClassLoader");
-    Constructor<?> pathClassLoaderInit = pathClassLoaderClass.getConstructor(String.class,
-                                                                             ClassLoader.class);
-    String path = String.join(":", elements);
-    return (ClassLoader) pathClassLoaderInit.newInstance(path, parent);
-  }
-
   private static class ClassNameComparator implements Comparator<Class<?>> {
     public int compare(Class<?> c1, Class<?> c2) {
       return c1.getName().compareTo(c2.getName());
     }
   }
+
+  // See run-test 910 for an explanation.
+
+  private static Class<?> proxyClass = null;
+
+  private static Class<?> getProxyClass() throws Exception {
+    if (proxyClass != null) {
+      return proxyClass;
+    }
+
+    for (int i = 1; i <= 21; i++) {
+      proxyClass = createProxyClass(i);
+      String name = proxyClass.getName();
+      if (name.equals("$Proxy20")) {
+        return proxyClass;
+      }
+    }
+    return proxyClass;
+  }
+
+  private static Class<?> createProxyClass(int i) throws Exception {
+    int count = Integer.bitCount(i);
+    Class<?>[] input = new Class<?>[count + 1];
+    input[0] = Runnable.class;
+    int inputIndex = 1;
+    int bitIndex = 0;
+    while (i != 0) {
+        if ((i & 1) != 0) {
+            input[inputIndex++] = Class.forName("art.Test912$I" + bitIndex);
+        }
+        i >>>= 1;
+        bitIndex++;
+    }
+    return Proxy.getProxyClass(Test912.class.getClassLoader(), input);
+  }
+
+  // Need this for the proxy naming.
+  public static interface I0 {
+  }
+  public static interface I1 {
+  }
+  public static interface I2 {
+  }
+  public static interface I3 {
+  }
+  public static interface I4 {
+  }
 }
diff --git a/test/912-classes/src/art/DexData.java b/test/912-classes/src/art/DexData.java
new file mode 100644
index 0000000..7d15032
--- /dev/null
+++ b/test/912-classes/src/art/DexData.java
@@ -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.
+ */
+
+package art;
+
+import java.nio.ByteBuffer;
+import java.util.Base64;
+
+import dalvik.system.InMemoryDexClassLoader;
+
+public class DexData {
+  public static ClassLoader getBootClassLoader() {
+    ClassLoader cl = DexData.class.getClassLoader();
+    while (cl.getParent() != null) {
+      cl = cl.getParent();
+    }
+    return cl;
+  }
+
+  public static ClassLoader create1() {
+    return create1(getBootClassLoader());
+  }
+  public static ClassLoader create1(ClassLoader parent) {
+    return create(parent, DEX_DATA_B);
+  }
+
+  public static ClassLoader create2() {
+    return create2(getBootClassLoader());
+  }
+  public static ClassLoader create2(ClassLoader parent) {
+    return create(parent, DEX_DATA_AC);
+  }
+
+  public static ClassLoader create12() {
+    return create12(getBootClassLoader());
+  }
+  public static ClassLoader create12(ClassLoader parent) {
+    return create(parent, DEX_DATA_AC, DEX_DATA_B);
+  }
+
+  private static ClassLoader create(ClassLoader parent, String... stringData) {
+    ByteBuffer byteBuffers[] = new ByteBuffer[stringData.length];
+    for (int i = 0; i < stringData.length; i++) {
+      byteBuffers[i] = ByteBuffer.wrap(Base64.getDecoder().decode(stringData[i]));
+    }
+    return new InMemoryDexClassLoader(byteBuffers, parent);
+  }
+
+  /*
+   * Derived from:
+   *
+   *   public class A {
+   *   }
+   *
+   *   public class C extends A {
+   *   }
+   *
+   */
+  private final static String DEX_DATA_AC =
+      "ZGV4CjAzNQD5KyH7WmGuqVEyL+2aKG1nyb27UJaCjFwQAgAAcAAAAHhWNBIAAAAAAAAAAIgBAAAH" +
+      "AAAAcAAAAAQAAACMAAAAAQAAAJwAAAAAAAAAAAAAAAMAAACoAAAAAgAAAMAAAAAQAQAAAAEAADAB" +
+      "AAA4AQAAQAEAAEgBAABNAQAAUgEAAGYBAAADAAAABAAAAAUAAAAGAAAABgAAAAMAAAAAAAAAAAAA" +
+      "AAAAAAABAAAAAAAAAAIAAAAAAAAAAAAAAAEAAAACAAAAAAAAAAEAAAAAAAAAcwEAAAAAAAABAAAA" +
+      "AQAAAAAAAAAAAAAAAgAAAAAAAAB9AQAAAAAAAAEAAQABAAAAaQEAAAQAAABwEAIAAAAOAAEAAQAB" +
+      "AAAAbgEAAAQAAABwEAAAAAAOAAY8aW5pdD4ABkEuamF2YQAGQy5qYXZhAANMQTsAA0xDOwASTGph" +
+      "dmEvbGFuZy9PYmplY3Q7AAFWABEABw4AEQAHDgAAAAEAAIGABIACAAABAAGBgASYAgALAAAAAAAA" +
+      "AAEAAAAAAAAAAQAAAAcAAABwAAAAAgAAAAQAAACMAAAAAwAAAAEAAACcAAAABQAAAAMAAACoAAAA" +
+      "BgAAAAIAAADAAAAAASAAAAIAAAAAAQAAAiAAAAcAAAAwAQAAAyAAAAIAAABpAQAAACAAAAIAAABz" +
+      "AQAAABAAAAEAAACIAQAA";
+
+  /*
+   * Derived from:
+   *
+   *   public class B {
+   *   }
+   *
+   */
+  private final static String DEX_DATA_B =
+      "ZGV4CjAzNQBgKV6iWFG4aOm5WEy8oGtDZjqsftBgwJ2oAQAAcAAAAHhWNBIAAAAAAAAAACABAAAF" +
+      "AAAAcAAAAAMAAACEAAAAAQAAAJAAAAAAAAAAAAAAAAIAAACcAAAAAQAAAKwAAADcAAAAzAAAAOQA" +
+      "AADsAAAA9AAAAPkAAAANAQAAAgAAAAMAAAAEAAAABAAAAAIAAAAAAAAAAAAAAAAAAAABAAAAAAAA" +
+      "AAAAAAABAAAAAQAAAAAAAAABAAAAAAAAABUBAAAAAAAAAQABAAEAAAAQAQAABAAAAHAQAQAAAA4A" +
+      "Bjxpbml0PgAGQi5qYXZhAANMQjsAEkxqYXZhL2xhbmcvT2JqZWN0OwABVgARAAcOAAAAAQAAgYAE" +
+      "zAEACwAAAAAAAAABAAAAAAAAAAEAAAAFAAAAcAAAAAIAAAADAAAAhAAAAAMAAAABAAAAkAAAAAUA" +
+      "AAACAAAAnAAAAAYAAAABAAAArAAAAAEgAAABAAAAzAAAAAIgAAAFAAAA5AAAAAMgAAABAAAAEAEA" +
+      "AAAgAAABAAAAFQEAAAAQAAABAAAAIAEAAA==";
+}
diff --git a/test/912-classes/src/art/Test912Art.java b/test/912-classes/src/art/Test912Art.java
new file mode 100644
index 0000000..e438473
--- /dev/null
+++ b/test/912-classes/src/art/Test912Art.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 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 art;
+
+import java.lang.ref.Reference;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+
+public class Test912Art {
+  public static void run() throws Exception {
+    art.Main.bindAgentJNIForClass(Test912Art.class);
+    doTest();
+  }
+
+  public static void doTest() throws Exception {
+    testClassEvents();
+  }
+
+  private static void testClassEvents() throws Exception {
+    // Note: the JIT part of this test is about the JIT pulling in a class not yet touched by
+    //       anything else in the system. This could be the verifier or the interpreter. We
+    //       block the interpreter by calling ensureJitCompiled. The verifier, however, must
+    //       run in configurations where dex2oat didn't verify the class itself. So explicitly
+    //       check whether the class has been already loaded, and skip then.
+    // TODO: Add multiple configurations to the run script once that becomes easier to do.
+    if (hasJit() && !isLoadedClass("art.Test912Art$ClassD")) {
+      testClassEventsJit();
+    }
+  }
+
+  private static void testClassEventsJit() throws Exception {
+    enableClassLoadSeenEvents(true);
+
+    testClassEventsJitImpl();
+
+    enableClassLoadSeenEvents(false);
+
+    if (!hadLoadEvent()) {
+      throw new RuntimeException("Did not get expected load event.");
+    }
+  }
+
+  private static void testClassEventsJitImpl() throws Exception {
+    ensureJitCompiled(Test912Art.class, "testClassEventsJitImpl");
+
+    if (ClassD.x != 1) {
+      throw new RuntimeException("Unexpected value");
+    }
+  }
+
+  private static native void ensureJitCompiled(Class<?> c, String name);
+
+  private static native boolean hasJit();
+  private static native boolean isLoadedClass(String name);
+  private static native void enableClassLoadSeenEvents(boolean b);
+  private static native boolean hadLoadEvent();
+
+  public static class ClassD {
+    static int x = 1;
+  }
+}
diff --git a/test/Android.bp b/test/Android.bp
index c5d96da..095b754 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -261,6 +261,7 @@
         "908-gc-start-finish/gc_callbacks.cc",
         "910-methods/methods.cc",
         "911-get-stack-trace/stack_trace.cc",
+        "912-classes/classes.cc",
         "913-heaps/heaps.cc",
         "918-fields/fields.cc",
         "920-objects/objects.cc",
@@ -295,7 +296,7 @@
         // make this list smaller.
         "901-hello-ti-agent/basics.cc",
         "909-attach-agent/attach.cc",
-        "912-classes/classes.cc",
+        "912-classes/classes_art.cc",
         "936-search-onload/search_onload.cc",
         "983-source-transform-verify/source_transform.cc",
     ],
diff --git a/test/Android.run-test-jvmti-java-library.mk b/test/Android.run-test-jvmti-java-library.mk
index dcb238c..70ee693 100644
--- a/test/Android.run-test-jvmti-java-library.mk
+++ b/test/Android.run-test-jvmti-java-library.mk
@@ -45,6 +45,8 @@
     911-get-stack-trace/src/art/Recurse.java \
     911-get-stack-trace/src/art/SameThread.java \
     911-get-stack-trace/src/art/ThreadListTraces.java \
+  912-classes/src/art/Test912.java \
+    912-classes/src/art/DexData.java \
   913-heaps/src/art/Test913.java \
   914-hello-obsolescence/src/art/Test914.java \
   915-obsolete-2/src/art/Test915.java \
@@ -84,6 +86,7 @@
   908 \
   910 \
   911 \
+  912 \
   913 \
   914 \
   915 \