1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *
15 *  See the License for the specific language governing permissions and
16 *  limitations under the License.
17 */
18
19package org.apache.harmony.jpda.tests.jdwp.share;
20
21import org.apache.harmony.jpda.tests.framework.TestErrorException;
22import org.apache.harmony.jpda.tests.framework.jdwp.CommandPacket;
23import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants;
24import org.apache.harmony.jpda.tests.framework.jdwp.ReplyPacket;
25import org.apache.harmony.jpda.tests.jdwp.share.debuggee.InvokeMethodWithSuspensionDebuggee;
26import org.apache.harmony.jpda.tests.share.JPDADebuggeeSynchronizer;
27
28import java.io.IOException;
29
30/**
31 * Base class for tests checking invoke command with thread suspension.
32 */
33public abstract class JDWPInvokeMethodWithSuspensionTestCase extends JDWPSyncTestCase {
34
35    @Override
36    protected final String getDebuggeeClassName() {
37        return InvokeMethodWithSuspensionDebuggee.class.getName();
38    }
39
40    /**
41     * This methods runs the {@link InvokeMethodWithSuspensionDebuggee} then sets up the
42     * following breakpoints:
43     * - breakpoint #1 to suspend the current thread only when the debuggee starts
44     * - breakpoint #2 to suspend all threads from a new thread started by a method called through
45     * JDWP.
46     * When we receive the event for breakpoint #1, we issue a request to invoke a method (or a
47     * constructor) in the event thread (suspended on the breakpoint). The event thread starts
48     * another child thread and loops as long as the child is running. However, we do not read the
49     * reply of the invoke yet.
50     * Next, we wait for the new thread to hit breakpoint #2. We resume all threads in the debuggee
51     * and read the reply of the invoke.
52     * Finally, we resume the thread that completed the invoke.
53     *
54     * @param invokedMethodName
55     *          the name of the method to invoke
56     */
57    protected void runInvokeMethodTest(String invokedMethodName) {
58        // Wait for debuggee to start.
59        synchronizer.receiveMessage(JPDADebuggeeSynchronizer.SGNL_READY);
60
61        long classID = getClassIDBySignature(getDebuggeeClassSignature());
62        long invokeMethodID = getMethodID(classID, invokedMethodName);
63
64        // Set breakpoint with EVENT_THREAD suspend policy so only the event thread is suspended.
65        // We will invoke the method in this thread.
66        int breakpointEventThread = debuggeeWrapper.vmMirror.setBreakpointAtMethodBegin(classID,
67                InvokeMethodWithSuspensionDebuggee.BREAKPOINT_EVENT_THREAD_METHOD_NAME,
68                JDWPConstants.SuspendPolicy.EVENT_THREAD);
69
70        // Set breakpoint with ALL suspend policy to suspend all threads. The thread started
71        // during the invoke will suspend all threads, including the thread executing the invoke.
72        int breakpointAllThreads = debuggeeWrapper.vmMirror.setBreakpointAtMethodBegin(classID,
73                InvokeMethodWithSuspensionDebuggee.BREAKPOINT_ALL_THREADS_METHOD_NAME,
74                JDWPConstants.SuspendPolicy.ALL);
75
76        // Tell the debuggee to continue.
77        synchronizer.sendMessage(JPDADebuggeeSynchronizer.SGNL_CONTINUE);
78
79        // Wait for breakpoint and get id of suspended thread.
80        long eventThreadOne = debuggeeWrapper.vmMirror.waitForBreakpoint(breakpointEventThread);
81        int suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadOne);
82        assertEquals("Invalid suspend count:", 1, suspendCount);
83
84        // Send command but does not read reply now. That invoked method starts another thread
85        // that is going to hit a breakpoint and suspend all threads, including the thread invoking
86        // the method. The invoke can only complete when that new thread terminates, which requires
87        // we send a VirtualMachine.Resume command.
88        final int invoke_options = 0;  // resume/suspend all threads before/after the invoke.
89        CommandPacket invokeMethodCommand = buildInvokeCommand(eventThreadOne, classID,
90                invokeMethodID, invoke_options);
91
92        String commandName = getInvokeCommandName();
93        logWriter.println("Send " + commandName);
94        int invokeMethodCommandID = -1;
95        try {
96            invokeMethodCommandID = debuggeeWrapper.vmMirror.sendCommand(invokeMethodCommand);
97        } catch (IOException e) {
98            logWriter.printError("Failed to send " + commandName, e);
99            fail();
100        }
101
102        // Wait for 2nd breakpoint to hit.
103        long eventThreadTwo = debuggeeWrapper.vmMirror.waitForBreakpoint(breakpointAllThreads);
104        suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadTwo);
105        assertEquals("Invalid suspend count:", 1, suspendCount);
106
107        // At this point, the event thread #1 must have been suspended by event thread #2. Since
108        // the invoke has resumed it too, its suspend count must remain 1.
109        suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadOne);
110        assertEquals("Invalid suspend count:", 1, suspendCount);
111
112        // Test that sending another invoke command in the same thread returns an error.
113        CommandPacket anotherInvokeMethodCommand = buildInvokeCommand(eventThreadOne, classID,
114                invokeMethodID, invoke_options);
115        ReplyPacket anotherInvokeMethodReply =
116                debuggeeWrapper.vmMirror.performCommand(anotherInvokeMethodCommand);
117        // The RI returns INVALID_THREAD error while ART returns ALREADY_INVOKING error which is
118        // more accurate. We only test we get an error so we can run the test with both runtimes.
119        assertTrue("Expected an error",
120                anotherInvokeMethodReply.getErrorCode() != JDWPConstants.Error.NONE);
121
122        // Send a VirtualMachine.Resume to resume all threads. This will unblock the event thread
123        // with the invoke in-progress.
124        logWriter.println("Resume all threads");
125        resumeDebuggee();
126
127        // Now we can read the invoke reply.
128        ReplyPacket invokeMethodReply = null;
129        try {
130            logWriter.println("Receiving reply for command " + invokeMethodCommandID + " ...");
131            invokeMethodReply = debuggeeWrapper.vmMirror.receiveReply(invokeMethodCommandID);
132        } catch (Exception e) {
133            throw new TestErrorException("Did not receive invoke reply", e);
134        }
135        checkReplyPacket(invokeMethodReply, commandName + " command");
136        logWriter.println("Received reply for command " + invokeMethodCommandID + " OK");
137
138        checkInvokeReply(invokeMethodReply);
139
140        // The invoke is complete but the thread is still suspended: let's resume it now.
141        suspendCount = debuggeeWrapper.vmMirror.getThreadSuspendCount(eventThreadOne);
142        assertEquals("Invalid suspend count:", 1, suspendCount);
143
144        logWriter.println("Resume event thread #1");
145        debuggeeWrapper.vmMirror.resumeThread(eventThreadOne);
146    }
147
148    /**
149     * Builds the packed for the tested JDWP command.
150     *
151     * @param threadId
152     *          the id of the thread that will invoke the method
153     * @param classID
154     *          the class ID of the invoked method
155     * @param methodId
156     *          the ID of the invoke method
157     * @param invokeOptions
158     *          options for the invoke
159     * @return a command
160     */
161    protected abstract CommandPacket buildInvokeCommand(long threadId, long classID,
162                                                        long methodId, int invokeOptions);
163
164    /**
165     * Returns the name of the command returned by {@link #buildInvokeCommand} for printing.
166     *
167     * @return the name of the invoke command sent to the debuggee
168     */
169    protected abstract String getInvokeCommandName();
170
171    /**
172     * Checks the reply for the tested JDWP command.
173     *
174     * @param reply the reply of the invoke
175     */
176    protected abstract void checkInvokeReply(ReplyPacket reply);
177
178}
179