Test class prepare event from the debugger

Checks that we properly receive CLASS_PREPARE event triggered by a
command (ReferenceType.GetValues) processed by the debugger thread.

Bug: 33032664
Test: art/tools/run-jdwp-tests.sh '--mode=host' '--variant=X64'

(cherry picked from commit e8a964ca684f4b2d72e69b79c08f248b81e4baca)

Change-Id: I4dbc354df1e6bb550d2826753fc0d7e88ded1418
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/Events/ClassPrepare002Debuggee.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/Events/ClassPrepare002Debuggee.java
new file mode 100644
index 0000000..9928bd4
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/Events/ClassPrepare002Debuggee.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.harmony.jpda.tests.jdwp.Events;
+
+import org.apache.harmony.jpda.tests.share.JPDADebuggeeSynchronizer;
+import org.apache.harmony.jpda.tests.share.SyncDebuggee;
+
+/**
+ * Debuggee for ClassPrepare002Test unit test.
+ */
+public class ClassPrepare002Debuggee extends SyncDebuggee {
+
+    public static void main(String[] args) {
+        runDebuggee(ClassPrepare002Debuggee.class);
+    }
+
+    @Override
+    public void run() {
+        logWriter.println("ClassPrepare002Debuggee started");
+
+        // Load TestClassA *without* initializing the class and notify the test.
+        try {
+            Class.forName("org.apache.harmony.jpda.tests.jdwp.Events.TestClassA", false,
+                    getClass().getClassLoader());
+        } catch (ClassNotFoundException e) {
+            e.printStackTrace();
+        }
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // Wait for the test to finish.
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        logWriter.println("ClassPrepare002Debuggee finished");
+    }
+}
+
+/**
+ * The class that will be investigated by the debugger for its static fields
+ * (ReferenceType.GetValues command).
+ */
+class TestClassA {
+    static TestClassB field = new TestClassB();
+}
+
+/**
+ * The class that will cause the CLASS PREPARE event when the debugger investigates static fields
+ * of the other class above.
+ */
+class TestClassB {
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/Events/ClassPrepare002Test.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/Events/ClassPrepare002Test.java
new file mode 100644
index 0000000..37a2c5a
--- /dev/null
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/jdwp/Events/ClassPrepare002Test.java
@@ -0,0 +1,136 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.harmony.jpda.tests.jdwp.Events;
+
+import java.io.IOException;
+import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.EventBuilder;
+import org.apache.harmony.jpda.tests.framework.jdwp.EventPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPCommands;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
+import org.apache.harmony.jpda.tests.framework.jdwp.ParsedEvent;
+import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
+import org.apache.harmony.jpda.tests.framework.jdwp.ParsedEvent.Event_CLASS_PREPARE;
+import org.apache.harmony.jpda.tests.share.JPDADebuggeeSynchronizer;
+
+/**
+ * JDWP unit test for CLASS_PREPARE events sent from the debugger thread.
+ */
+public class ClassPrepare002Test extends JDWPEventTestCase {
+    @Override
+    protected String getDebuggeeClassName() {
+        return ClassPrepare002Debuggee.class.getName();
+    }
+
+    /**
+     * Tests that the CLASS_PREPARE event can be sent from the runtime's debugger thread.
+     *
+     * This test starts by requesting a CLASS_PREPARE event then sends a ReferenceType.GetValues
+     * command to cause the debugger thread to report a CLASS_PREPARE event. After checking the
+     * content of the event packet, we clear the event request then resume the debuggee.
+     */
+    public void testClassPrepareCausedByDebugger() {
+        logWriter.println("testClassPrepareCausedByDebugger started");
+
+        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_READY);
+
+        // The TestClassA must be loaded but not initialized.
+        long classIDOfA = getClassIDBySignature(getClassSignature(TestClassA.class));
+
+        // Restrict class prepare only to the tested class.
+        String classPrepareClassName = "org.apache.harmony.jpda.tests.jdwp.Events.TestClassB";
+
+        // Request CLASS_PREPARE event. The event will be caused by the debugger thread when
+        // handling the ReferenceType.GetValues command we're going to send just after.
+        EventBuilder eventBuilder = new EventBuilder(JDWPConstants.EventKind.CLASS_PREPARE,
+                JDWPConstants.SuspendPolicy.EVENT_THREAD);
+        eventBuilder.setClassMatch(classPrepareClassName);
+        ReplyPacket eventReply = debuggeeWrapper.vmMirror.setEvent(eventBuilder.build());
+        checkReplyPacket(eventReply, "Failed to set CLASS_PREPARE event");
+        int classPrepareRequestId = eventReply.getNextValueAsInt();
+        assertAllDataRead(eventReply);
+
+        // Send a ReferenceType.GetValues command to force class initialization. This will trigger
+        // the CLASS_PREPARE event from the debugger thread.
+        logWriter.println("=> CHECK: send ReferenceType.GetValues");
+        long fieldId = checkField(classIDOfA, "field");
+        CommandPacket getValuesCommand = new CommandPacket(
+                JDWPCommands.ReferenceTypeCommandSet.CommandSetID,
+                JDWPCommands.ReferenceTypeCommandSet.GetValuesCommand);
+        getValuesCommand.setNextValueAsReferenceTypeID(classIDOfA);
+        getValuesCommand.setNextValueAsInt(1);
+        getValuesCommand.setNextValueAsFieldID(fieldId);
+
+        // Send command but does not wait for reply because this will cause a CLASS_PREPARE event
+        // that will suspend every thread.
+        int rtGetValuesCommandId = -1;
+        try {
+            rtGetValuesCommandId = debuggeeWrapper.vmMirror.sendCommand(getValuesCommand);
+        } catch (IOException e) {
+            fail("Failed to send ReferenceType.GetValues command");
+        }
+
+        // Wait for the class prepare event.
+        EventPacket eventPacket = debuggeeWrapper.vmMirror
+                .receiveCertainEvent(JDWPConstants.EventKind.CLASS_PREPARE);
+        ParsedEvent[] parsedEvents = ParsedEvent.parseEventPacket(eventPacket);
+        assertNotNull(parsedEvents);
+        logWriter.println("Received " + parsedEvents.length + " event(s)");
+        assertEquals(1, parsedEvents.length);
+
+        // Check event is the expected one.
+        ParsedEvent parsedEvent = parsedEvents[0];
+        // Only expect CLASS_PREPARE event.
+        assertEquals(JDWPConstants.EventKind.CLASS_PREPARE, parsedEvent.getEventKind());
+        // CLASS_PREPARE events caused by the debugger must have an ALL
+        // suspend policy.
+        assertEquals(JDWPConstants.SuspendPolicy.ALL, parsedEvent.getSuspendPolicy());
+        // Check that its the event that we have requested.
+        assertEquals(classPrepareRequestId, parsedEvent.getRequestID());
+        Event_CLASS_PREPARE classPrepareEvent = (Event_CLASS_PREPARE) parsedEvent;
+        // The JDWP spec says that if the event was caused by the debugger thread, the thread ID
+        // must be 0.
+        assertEquals(0, classPrepareEvent.getThreadID());
+        // Check that it is the expected class.
+        assertEquals(getClassSignature(classPrepareClassName), classPrepareEvent.getSignature());
+
+        // Check that we received the reply of the command that caused the CLASS_PREPARE event.
+        logWriter.println("=> CHECK: receive ReferenceType.GetValues reply (" +
+                rtGetValuesCommandId + ")");
+        ReplyPacket getValuesReply = null;
+        try {
+            getValuesReply = debuggeeWrapper.vmMirror.receiveReply(rtGetValuesCommandId);
+        } catch (InterruptedException | IOException e) {
+            fail("Failed to receive ReferenceType.GetValues reply");
+        }
+        checkReplyPacket(getValuesReply, "Failed to receive ReferenceType.GetValues reply");
+
+        // Clear the CLASS_PREPARE event request.
+        debuggeeWrapper.vmMirror.clearEvent(JDWPConstants.EventKind.CLASS_PREPARE,
+                classPrepareRequestId);
+
+        // Resume all threads suspended by the CLASS_PREPARE event.
+        debuggeeWrapper.vmMirror.resume();
+
+        // Let the debuggee finish.
+        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
+
+        logWriter.println("testClassPrepareCausedByDebugger finished");
+    }
+}
diff --git a/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java b/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java
index 44e4211..69b7c8b 100644
--- a/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java
+++ b/jdwp/src/test/java/org/apache/harmony/jpda/tests/share/AllTests.java
@@ -90,6 +90,7 @@
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.Events.BreakpointMultipleTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.Events.BreakpointOnCatchTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.Events.BreakpointTest.class);
+    suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.Events.ClassPrepare002Test.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.Events.ClassPrepareTest.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.Events.CombinedEvents002Test.class);
     suite.addTestSuite(org.apache.harmony.jpda.tests.jdwp.Events.CombinedEvents003Test.class);