8306471: Add virtual threads support to JDWP ThreadReference.Stop and JDI ThreadReference.stop()

Reviewed-by: sspitsyn, alanb
This commit is contained in:
Chris Plummer 2023-05-12 20:05:21 +00:00
parent 4441a2306f
commit d809823fe4
7 changed files with 366 additions and 88 deletions

View File

@ -2004,10 +2004,11 @@ JDWP "Java(tm) Debug Wire Protocol"
(Command Stop=10
"Stops the thread with an asynchronous exception. "
"<p>"
"The target VM may not support, or may only provide limited support, for "
"this command when the thread is a virtual thread. It may, for example, "
"only support this command when the virtual thread is suspended at a "
"breakpoint or singlestep event."
"This command may be used to send an asynchronous "
"exception to a virtual thread when it is suspended at an event. "
"An implementation may support sending an asynchronous exception "
"to a suspended virtual thread in other cases."
(Out
(threadObject thread "The thread object ID. ")
(object throwable "Asynchronous exception. This object must "
@ -2018,8 +2019,10 @@ JDWP "Java(tm) Debug Wire Protocol"
(ErrorSet
(Error INVALID_THREAD "The thread is null, not a valid thread, or the thread "
"is not alive.")
(Error NOT_IMPLEMENTED "The thread is a virtual thread and the target "
"VM does not support the command on virtual threads.")
(Error THREAD_NOT_SUSPENDED "The thread is a virtual thread and was not suspended.")
(Error OPAQUE_FRAME "The thread is a suspended virtual thread and the implementation "
"was unable to throw an asynchronous exception "
"from the thread's current frame.")
(Error INVALID_OBJECT "If thread is not a known ID or the asynchronous "
"exception has been garbage collected.")
(Error VM_DEAD)
@ -3166,7 +3169,7 @@ JDWP "Java(tm) Debug Wire Protocol"
"call stack.")
(Constant OPAQUE_FRAME =32 "Information about the frame is not available "
"(e.g. native frame) or the target VM is unable "
"to perform an operation on the frame.")
"to perform an operation on the thread's current frame.")
(Constant NOT_CURRENT_FRAME =33 "Operation can only be performed on current frame.")
(Constant TYPE_MISMATCH =34 "The variable is not an appropriate type for "
"the function used.")

View File

@ -116,18 +116,20 @@ public interface ThreadReference extends ObjectReference {
* A debugger thread in the target VM will stop this thread
* with the given {@link java.lang.Throwable} object.
* <p>
* The target VM may not support, or may only provide limited support,
* for stopping a virtual thread with an asynchronous exception. It may,
* for example, only support this operation when the virtual thread is
* suspended at a breakpoint or singlestep event.
* This method may be used to send an asynchronous
* exception to a virtual thread when it is suspended at an event.
* An implementation may support sending an asynchronous exception
* to a suspended virtual thread in other cases.
*
* @param throwable the asynchronous exception to throw
* @throws InvalidTypeException if <code>throwable</code> is not
* an instance of java.lang.Throwable in the target VM
* @throws IllegalThreadStateException if the thread has terminated
* @throws UnsupportedOperationException if the thread is a virtual
* thread and the target VM does not support this operation on
* virtual threads
* @throws IllegalThreadStateException if the thread has terminated,
* or if the thread is a virtual thread and was not suspended
* @throws OpaqueFrameException if the thread is a suspended
* virtual thread and the implementation was unable to throw an
* asynchronous exception from the thread's current frame
* @throws VMCannotBeModifiedException if the VirtualMachine is read-only
* @see VirtualMachine#canBeModified()
*/

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -719,9 +719,13 @@ class Commands {
} catch (InvalidTypeException e) {
MessageOutput.println("Invalid exception object");
} catch (IllegalThreadStateException its) {
MessageOutput.println("Illegal thread state");
} catch (UnsupportedOperationException uoe) {
MessageOutput.println("Operation is not supported on the target VM");
if (!thread.isSuspended() && thread.isVirtual()) {
MessageOutput.println("Illegal thread state (virtual thread not suspended)");
} else {
MessageOutput.println("Illegal thread state");
}
} catch (OpaqueFrameException ope) {
MessageOutput.println("Operation is not supported on the current frame");
}
} else {
MessageOutput.println("Expression must evaluate to an object");

View File

@ -142,6 +142,7 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Illegal Argument Exception", "Illegal Argument Exception"},
{"Illegal connector argument", "Illegal connector argument: {0}"},
{"Illegal thread state", "Illegal thread state"},
{"Illegal thread state (virtual thread not suspended)", "Illegal thread state (virtual thread not suspended)"},
{"implementor:", "implementor: {0}"},
{"implements:", "implements: {0}"},
{"Initializing progname", "Initializing {0} ..."},
@ -244,7 +245,7 @@ public class TTYResources extends java.util.ListResourceBundle {
{"Not waiting for a monitor", " Not waiting for a monitor"},
{"Nothing suspended.", "Nothing suspended."},
{"object description and id", "({0}){1}"},
{"Operation is not supported on the target VM", "Operation is not supported on the target VM"},
{"Operation is not supported on the current frame", "Operation is not supported on the current frame"},
{"operation not yet supported", "operation not yet supported"},
{"Owned by:", " Owned by: {0}, entry count: {1,number,integer}"},
{"Owned monitor:", " Owned monitor: {0}"},

View File

@ -273,7 +273,18 @@ public class ThreadReferenceImpl extends ObjectReferenceImpl
JDWP.ThreadReference.Stop.process(vm, this,
(ObjectReferenceImpl)throwable);
} catch (JDWPException exc) {
throw exc.toJDIException();
switch (exc.errorCode()) {
case JDWP.Error.OPAQUE_FRAME:
assert isVirtual(); // can only happen with virtual threads
throw new OpaqueFrameException();
case JDWP.Error.THREAD_NOT_SUSPENDED:
assert isVirtual(); // can only happen with virtual threads
throw new IllegalThreadStateException("virtual thread not suspended");
case JDWP.Error.INVALID_THREAD:
throw new IllegalThreadStateException("thread has terminated");
default:
throw exc.toJDIException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,16 +35,21 @@ import nsk.share.jpda.*;
import nsk.share.jdi.*;
/**
* The test checks that the JDI method:<br>
* <code>com.sun.jdi.ThreadReference.stop()</code><br>
* properly throws <i>InvalidTypeException</i> - if specified
* throwable is not an instance of java.lang.Throwable in the
* target VM.<p>
* The test checks that the JDI method:<br><code>com.sun.jdi.ThreadReference.stop()</code><br>
* behaves properly in various situations. It consists of 5 subtests.
*
* Debugger part of the test tries to stop debuggee thread
* through the JDI method using as a parameter an object
* reference of the main debuggee class <i>stop002t</i> itself
* which is not <code>Throwable</code>.
* TEST #1: Tests that stop() properly throws <i>InvalidTypeException</i> if
* specified throwable is not an instance of java.lang.Throwable in the target VM.<p>
*
* TEST #2: Verify that stop() works when suspended at a breakpoint.
*
* TEST #3: Verify that stop() works when not suspended in a loop. For virtual threads
* we expect an IncompatibleThreadStateException.
*
* TEST #4: Verify that stop() works when suspended in a loop.
*
* TEST #5: Verify that stop() works when suspended in Thread.sleep(). For virtual
* threads we expect an OpaqueFrameException.
*/
public class stop002 {
static final String DEBUGGEE_CLASS =
@ -54,12 +59,15 @@ public class stop002 {
static final String DEBUGGEE_THRNAME = "stop002tThr";
// debuggee local var used to find needed non-throwable object
static final String DEBUGGEE_LOCALVAR = "stop002tNonThrowable";
// debuggee field used to indicate that testing is over
static final String DEBUGGEE_FIELD = "stopLooping";
static final String DEBUGGEE_NON_THROWABLE_VAR= "stop002tNonThrowable";
// debuggee local var used to find needed throwable object
static final String DEBUGGEE_THROWABLE_VAR = "stop002tThrowable";
// debuggee fields used to indicate to exit infinite loops
static final String DEBUGGEE_STOP_LOOP1_FIELD = "stopLooping1";
static final String DEBUGGEE_STOP_LOOP2_FIELD = "stopLooping2";
// debuggee source line where it should be stopped
static final int DEBUGGEE_STOPATLINE = 69;
static final int DEBUGGEE_STOPATLINE = 88;
static final int DELAY = 500; // in milliseconds
@ -67,12 +75,15 @@ public class stop002 {
static final String COMMAND_GO = "go";
static final String COMMAND_QUIT = "quit";
static final boolean vthreadMode = "Virtual".equals(System.getProperty("main.wrapper"));
private ArgumentHandler argHandler;
private Log log;
private IOPipe pipe;
private Debugee debuggee;
private VirtualMachine vm;
private BreakpointRequest BPreq;
private ReferenceType mainClass;
private volatile int tot_res = Consts.TEST_PASSED;
private volatile boolean gotEvent = false;
@ -101,68 +112,180 @@ public class stop002 {
return quitDebuggee();
}
ThreadReference thrRef = null;
if ((thrRef =
debuggee.threadByName(DEBUGGEE_THRNAME)) == null) {
ThreadReference thrRef = debuggee.threadByName(DEBUGGEE_THRNAME);
if (thrRef == null) {
log.complain("TEST FAILURE: method Debugee.threadByName() returned null for debuggee thread "
+ DEBUGGEE_THRNAME);
tot_res = Consts.TEST_FAILED;
return quitDebuggee();
}
Field doExit = null;
Field stopLoop1 = null;
Field stopLoop2 = null;
ObjectReference objRef = null;
ObjectReference throwableRef = null;
try {
// debuggee main class
ReferenceType rType = debuggee.classByName(DEBUGGEE_CLASS);
mainClass = debuggee.classByName(DEBUGGEE_CLASS);
suspendAtBP(rType, DEBUGGEE_STOPATLINE);
objRef = findObjRef(thrRef, DEBUGGEE_LOCALVAR);
suspendAtBP(mainClass, DEBUGGEE_STOPATLINE);
objRef = findObjRef(thrRef, DEBUGGEE_NON_THROWABLE_VAR);
throwableRef = findObjRef(thrRef, DEBUGGEE_THROWABLE_VAR);
// this field is used to indicate that debuggee has to
// stop looping
doExit = rType.fieldByName(DEBUGGEE_FIELD);
log.display("Resuming debuggee VM ...");
vm.resume();
log.display("Resumption of debuggee VM done");
log.display("\nTrying to stop debuggee thread \"" + thrRef
+ "\"\n\tusing non-throwable object reference \""
+ objRef + "\" as a parameter ...");
// Check the tested assersion
try {
thrRef.stop(objRef);
log.complain("TEST FAILED: expected IllegalArgumentException was not thrown"
+ "\n\twhen attempted to stop debuggee thread \"" + thrRef
+ "\"\n\tusing non-throwable object reference \""
+ objRef + "\" as a parameter");
tot_res = Consts.TEST_FAILED;
} catch(InvalidTypeException ee) {
log.display("CHECK PASSED: caught expected " + ee);
} catch(Exception ue) {
ue.printStackTrace();
log.complain("TEST FAILED: ThreadReference.stop(): caught unexpected "
+ ue + "\n\tinstead of InvalidTypeException"
+ "\n\twhen attempted to stop debuggee thread \"" + thrRef
+ "\"\n\tusing non-throwable object reference \""
+ objRef + "\" as a parameter");
tot_res = Consts.TEST_FAILED;
// These fields are used to indicate that debuggee has to stop looping
stopLoop1 = mainClass.fieldByName(DEBUGGEE_STOP_LOOP1_FIELD);
stopLoop2 = mainClass.fieldByName(DEBUGGEE_STOP_LOOP2_FIELD);
if (stopLoop1 == null || stopLoop2 == null) {
throw new RuntimeException("Failed to find a \"stop loop\" field");
}
log.display("non-throwable object: \"" + objRef + "\"");
log.display("throwable object: \"" + throwableRef + "\"");
log.display("debuggee thread: \"" + thrRef + "\"");
/*
* Test #1: verify using a non-throwable object with stop() fails appropriately.
*/
log.display("\nTEST #1: Trying to stop debuggee thread using non-throwable object.");
try {
thrRef.stop(objRef); // objRef is an instance of the debuggee class, not a Throwable
log.complain("TEST #1 FAILED: expected InvalidTypeException was not thrown");
tot_res = Consts.TEST_FAILED;
} catch (InvalidTypeException ee) {
log.display("TEST #1 PASSED: caught expected " + ee);
} catch (Exception ue) {
ue.printStackTrace();
log.complain("TEST #1 FAILED: caught unexpected " + ue + "instead of InvalidTypeException");
tot_res = Consts.TEST_FAILED;
}
log.display("TEST #1: all done.");
/*
* Test #2: verify that stop() works when suspended at a breakpoint.
*/
log.display("\nTEST #2: Trying to stop debuggee thread while suspended at a breakpoint.");
try {
thrRef.stop(throwableRef);
log.display("TEST #2 PASSED: stop() call succeeded.");
} catch (Exception ue) {
ue.printStackTrace();
log.complain("TEST #2 FAILED: caught unexpected " + ue);
tot_res = Consts.TEST_FAILED;
}
log.display("TEST #2: Resuming debuggee VM to allow async exception to be handled");
vm.resume();
log.display("TEST #2: all done.");
/*
* Test #3: verify that stop() works when not suspended in a loop. Expect
* IllegalThreadStateException for virtual threads.
*/
log.display("\nTEST #3: Trying to stop debuggee thread while not suspended in a loop.");
waitForTestReady(3);
try {
thrRef.stop(throwableRef);
if (vthreadMode) {
log.complain("TEST #3 FAILED: expected IllegalThreadStateException"
+ " was not thrown for virtual thread");
tot_res = Consts.TEST_FAILED;
} else {
log.display("TEST #3 PASSED: stop() call succeeded.");
}
} catch (Exception ue) {
if (vthreadMode && ue instanceof IllegalThreadStateException) {
log.display("TEST #3 PASSED: stop() call threw IllegalThreadStateException"
+ " for virtual thread");
} else {
ue.printStackTrace();
log.complain("TEST #3 FAILED: caught unexpected " + ue);
tot_res = Consts.TEST_FAILED;
}
} finally {
// Force the debuggee out of the loop. Not really needed if the stop() call
// successfully threw the async exception, but it's easier to just always do this.
log.display("TEST #3: clearing loop flag.");
objRef.setValue(stopLoop1, vm.mirrorOf(true));
}
log.display("TEST #3: all done.");
/*
* Test #4: verify that stop() works when suspended in a loop
*/
log.display("\nTEST #4: Trying to stop debuggee thread while suspended in a loop.");
waitForTestReady(4);
try {
thrRef.suspend();
log.display("TEST #4: thread is suspended.");
thrRef.stop(throwableRef);
log.display("TEST #4 PASSED: stop() call succeeded.");
} catch (Throwable ue) {
ue.printStackTrace();
log.complain("TEST #4 FAILED: caught unexpected " + ue);
tot_res = Consts.TEST_FAILED;
} finally {
log.display("TEST #4: resuming thread.");
thrRef.resume();
// Force the debuggee out of the loop. Not really needed if the stop() call
// successfully threw the async exception, but it's easier to just always do this.
log.display("TEST #4: clearing loop flag.");
objRef.setValue(stopLoop2, vm.mirrorOf(true));
}
log.display("TEST #4: all done.");
/*
* Test #5: verify that stop() works when suspended in Thread.sleep(). Expect
* OpaqueFrameException for virtual threads.
*/
log.display("\nTEST #5: Trying to stop debuggee thread while suspended in Thread.sleep().");
waitForTestReady(5);
// Allow debuggee to reach Thread.sleep() first.
log.display("TEST #5: waiting for debuggee to sleep...");
while (true) {
int status = thrRef.status();
if (status == ThreadReference.THREAD_STATUS_SLEEPING ||
status == ThreadReference.THREAD_STATUS_WAIT)
{
break;
}
Thread.sleep(50);
}
log.display("TEST #5: debuggee is sleeping.");
try {
thrRef.suspend();
log.display("TEST #5: thread is suspended.");
thrRef.stop(throwableRef);
if (vthreadMode) {
log.complain("TEST #5 FAILED: expected OpaqueFrameException was not thrown");
tot_res = Consts.TEST_FAILED;
} else {
log.display("TEST #5 PASSED: stop() call for suspended thread succeeded");
}
} catch (Throwable ue) {
if (vthreadMode && ue instanceof OpaqueFrameException) {
log.display("TEST #5 PASSED: stop() call threw OpaqueFrameException for virtual thread");
} else {
ue.printStackTrace();
log.complain("TEST #5 FAILED: caught unexpected " + ue);
tot_res = Consts.TEST_FAILED;
}
} finally {
log.display("TEST #5: resuming thread.");
thrRef.resume();
}
log.display("TEST #5: all done.");
} catch (Exception e) {
e.printStackTrace();
log.complain("TEST FAILURE: caught unexpected exception: " + e);
tot_res = Consts.TEST_FAILED;
} finally {
// Finish the test
// force an method to exit
if (objRef != null && doExit != null) {
// Force the debuggee out of both loops
if (objRef != null && stopLoop1 != null && stopLoop2 != null) {
try {
objRef.setValue(doExit, vm.mirrorOf(true));
} catch(Exception sve) {
objRef.setValue(stopLoop1, vm.mirrorOf(true));
objRef.setValue(stopLoop2, vm.mirrorOf(true));
} catch (Exception sve) {
sve.printStackTrace();
tot_res = Consts.TEST_FAILED;
}
}
}
@ -170,6 +293,15 @@ public class stop002 {
return quitDebuggee();
}
private void waitForTestReady(int testNum) {
log.display("TEST #" + testNum + ": waiting for test ready...");
IntegerValue ival;
do {
ival = (IntegerValue)mainClass.getValue(mainClass.fieldByName("testNumReady"));
} while (ival.value() != testNum);
log.display("TEST #" + testNum + ": test ready.");
}
private ObjectReference findObjRef(ThreadReference thrRef, String varName) {
try {
List frames = thrRef.frames();
@ -184,9 +316,9 @@ public class stop002 {
return (ObjectReference)
stackFr.getValue(locVar);
}
} catch(AbsentInformationException e) {
} catch (AbsentInformationException e) {
// this is not needed stack frame, ignoring
} catch(NativeMethodException ne) {
} catch (NativeMethodException ne) {
// current method is native, also ignoring
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,7 +35,10 @@ import nsk.share.jdi.*;
public class stop002t {
private Log log;
private IOPipe pipe;
volatile boolean stopLooping = false;
volatile boolean stopLooping1 = false;
volatile boolean stopLooping2 = false;
volatile static int testNumReady = 0;
static final boolean vthreadMode = "Virtual".equals(System.getProperty("main.wrapper"));
public static void main(String args[]) {
System.exit(run(args) + Consts.JCK_STATUS_BASE);
@ -57,23 +60,138 @@ public class stop002t {
// as wrong parameter of JDI method ThreadReference.stop()
stop002t stop002tNonThrowable = this;
// throwable object which will be used by debugger
// as valid parameter of JDI method ThreadReference.stop()
Throwable stop002tThrowable = new MyThrowable("Async exception");
// Now the debuggee is ready for testing
pipe.println(stop002.COMMAND_READY);
String cmd = pipe.readln();
if (cmd.equals(stop002.COMMAND_QUIT)) {
log.complain("Debuggee: exiting due to the command "
log.complain("Debuggee: premature debuggee exit due to the command "
+ cmd);
return Consts.TEST_PASSED;
return Consts.TEST_FAILED;
}
int stopMeHere = 0; // stop002.DEBUGGEE_STOPATLINE
/*
* TEST #1: Tests that stop() properly throws InvalidTypeException if
* the specified throwable is not an instance of java.lang.Throwable
* in the debuggee. It does not involve the debuggee at all, so there
* is no code here for it.
*/
log.display("Debuggee: going to loop ...");
while(!stopLooping) { // looping
stopMeHere++; stopMeHere--;
/*
* TEST #2: async exception while suspended at a breakpoint.
*/
int stopMeHere = 0;
try {
stopMeHere = 1; // stop002.DEBUGGEE_STOPATLINE
log.complain("TEST #2: Failed to throw expected exception");
return Consts.TEST_FAILED;
} catch (Throwable t) {
// Call Thread.interrupted(). Workaround for JDK-8306324
log.display("TEST #2: interrupted = " + Thread.interrupted());
if (t instanceof MyThrowable) {
log.display("TEST #2: Caught expected exception while at breakpoint: " + t);
} else {
log.complain("TEST #2: Unexpected exception caught: " + t);
t.printStackTrace();
return Consts.TEST_FAILED;
}
}
log.display("Debuggee: looping done");
log.display("TEST #2: all done");
/*
* TEST #3: async exception while not suspended in a loop.
*/
log.display("TEST #3: going to loop ...");
try {
while (!stopLooping1) { // looping
testNumReady = 3; // signal debugger side of test that we are ready
stopMeHere++; stopMeHere--;
}
if (vthreadMode) {
log.display("TEST #3: Correctly did not throw async exception for virtual thread");
} else {
log.complain("TEST #3: Failed to throw expected exception");
return Consts.TEST_FAILED;
}
} catch (Throwable t) {
// Call Thread.interrupted(). Workaround for JDK-8306324
log.display("TEST #3: interrupted = " + Thread.interrupted());
// We don't expect the exception to be thrown when in vthread mode.
if (!vthreadMode && t instanceof MyThrowable) {
log.display("TEST #3: Caught expected exception while in loop: " + t);
} else {
log.complain("TEST #3: Unexpected exception caught: " + t);
t.printStackTrace();
return Consts.TEST_FAILED;
}
}
log.display("TEST #3: all done");
/*
* TEST #4: async exception while suspended in a loop.
*/
log.display("TEST #4: going to loop ...");
try {
while (!stopLooping2) { // looping
testNumReady = 4; // signal debugger side of test that we are ready
stopMeHere++; stopMeHere--;
}
log.complain("TEST #4: Failed to throw expected exception");
return Consts.TEST_FAILED;
} catch (Throwable t) {
// Call Thread.interrupted(). Workaround for JDK-8306324
log.display("TEST #4: interrupted = " + Thread.interrupted());
if (t instanceof MyThrowable) {
log.display("TEST #4: Caught expected exception while in loop: " + t);
} else {
log.complain("TEST #4: Unexpected exception caught: " + t);
t.printStackTrace();
return Consts.TEST_FAILED;
}
}
log.display("TEST #4: all done");
/*
* TEST #5: async exception while suspended doing Thread.sleep().
*/
try {
try {
// Signal debugger side of test that we are "almost" ready. The
// debugger will still need to check that we are in the sleep state.
testNumReady = 5;
log.display("TEST #5: going to sleep ...");
Thread.sleep(10000);
} catch (InterruptedException e) {
log.complain("TEST #5: Unexpected InterruptedException");
e.printStackTrace();
return Consts.TEST_FAILED;
}
if (vthreadMode) {
log.display("TEST #5: Correctly did not throw exception while in sleep");
} else {
log.complain("TEST #5: Failed to throw expected exception");
return Consts.TEST_FAILED;
}
} catch (Throwable t) {
// Call Thread.interrupted(). Workaround for JDK-8306324
log.display("TEST #5: interrupted = " + Thread.interrupted());
// We don't expect the exception to be thrown when in vthread mode.
if (!vthreadMode && t instanceof MyThrowable) {
log.display("TEST #5: Caught expected exception while in loop: " + t);
} else {
log.complain("TEST #5: Unexpected exception caught: " + t);
t.printStackTrace();
return Consts.TEST_FAILED;
}
}
log.display("TEST #5: all done");
/*
* Test shutdown.
*/
cmd = pipe.readln();
if (!cmd.equals(stop002.COMMAND_QUIT)) {
log.complain("TEST BUG: unknown debugger command: "
@ -83,3 +201,10 @@ public class stop002t {
return Consts.TEST_PASSED;
}
}
class MyThrowable extends Throwable
{
MyThrowable(String message) {
super(message);
}
}