Over a million developers have joined DZone.

Write an Auto-debugger to Catch Exceptions During Test Execution

DZone's Guide to

Write an Auto-debugger to Catch Exceptions During Test Execution

· Performance Zone ·
Free Resource

Sensu is an open source monitoring event pipeline. Try it today.

Previously I have stated that there are some exceptions you would always want to keep a debugger breakpoint on for. This helps prevent code rotting away without you noticing -- but this sometimes masks a different problem.

If you take this seriously then it is a good idea to extend this idea to your automated testing, but coming up with a comprehensive solution is not entirely trivial. You could just start with a try/catch but that won't capture exceptions on other threads. You could also do something using AOP, but depending on the framework you are not guaranteed to catch everything and it does mean that you are testing with slightly different code, which will worry some.

A few days ago I came across this blog entry on how to write your own debugger, and I wondered if it was possible for a Java process to debug itself. Turns out, you can, and here is the code I came up with as part of this little thought experiment.

The first part of the class just contains some fairly hacky code to guess what port would be required to connect back to the same VM based on the start-up parameters. It might be possible to use the Attach mechanism to start the debugger, but I didn't see an obvious way to get it to work. Then there are just a couple of factory methods that take a list of exceptions to look out for.

    package com.kingsfleet.debug;  
    import com.sun.jdi.Bootstrap;  
    import com.sun.jdi.ReferenceType;  
    import com.sun.jdi.VirtualMachine;  
    import com.sun.jdi.connect.AttachingConnector;  
    import com.sun.jdi.connect.Connector;  
    import com.sun.jdi.connect.IllegalConnectorArgumentsException;  
    import com.sun.jdi.event.ClassPrepareEvent;  
    import com.sun.jdi.event.Event;  
    import com.sun.jdi.event.EventQueue;  
    import com.sun.jdi.event.EventSet;  
    import com.sun.jdi.event.ExceptionEvent;  
    import com.sun.jdi.event.VMDeathEvent;  
    import com.sun.jdi.event.VMDisconnectEvent;  
    import com.sun.jdi.request.ClassPrepareRequest;  
    import com.sun.jdi.request.EventRequest;  
    import com.sun.jdi.request.ExceptionRequest;  
    import java.io.IOException;  
    import java.lang.management.ManagementFactory;  
    import java.lang.management.RuntimeMXBean;  
    import java.util.Collections;  
    import java.util.HashSet;  
    import java.util.List;  
    import java.util.Map;  
    import java.util.Set;  
    import java.util.concurrent.CountDownLatch;  
    import java.util.concurrent.TimeUnit;  
    public class ExceptionDebugger implements AutoCloseable {  
       public static int getDebuggerPort() {  
           // Try to work out what port we need to connect to  
           RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();  
           List<String> inputArguments = runtime.getInputArguments();  
           int port = -1;  
           boolean isjdwp = false;  
           for (String next : inputArguments) {  
               if (next.startsWith("-agentlib:jdwp=")) {  
                   isjdwp = true;  
                   String parameterString = next.substring("-agentlib:jdwp=".length());  
                   String[] parameters = parameterString.split(",");  
                   for (String parameter : parameters) {  
                       if (parameter.startsWith("address")) {  
                           int portDelimeter = parameter.lastIndexOf(":");  
                           if (portDelimeter != -1) {  
                               port = Integer.parseInt(parameter.substring(portDelimeter + 1));  
                           } else {  
                               port = Integer.parseInt(parameter.split("=")[1]);  
           return port;  
       public static ExceptionDebugger connect(final String... exceptions) throws InterruptedException {  
           return connect(getDebuggerPort(),exceptions);  
       public static ExceptionDebugger connect(final int port, final String... exceptions) throws InterruptedException {  
           ExceptionDebugger ed = new ExceptionDebugger(port, exceptions);  
           return ed;  
The constructor creates a simple daemon thread that starts the connection back to the VM. It is very important to do this in a separate thread, otherwise obviously the VM will grind to a halt when we hit a breakpoint. It is a good idea to make sure that code in that thread doesn't throw one of the exceptions -- for the moment I am just hoping for the best.

Finally, the code just maintains a list of the banned exceptions, and if you had a bit more time it should be possible to store the stack trace where the exception occurred.
       // Instance variables  
       private final CountDownLatch startupLatch = new CountDownLatch(1);  
       private final CountDownLatch shutdownLatch = new CountDownLatch(1);  
       private final Set<String> set = Collections.synchronizedSet(new HashSet<String>());  
       private final int port;  
       private final String exceptions[];  
       private Thread debugger;  
       private volatile boolean shutdown = false;  
       // Object construction and methods  
       private ExceptionDebugger(final int port, final String... exceptions) throws InterruptedException {  
           this.port = port;  
           this.exceptions = exceptions;  
           debugger = new Thread(new Runnable() {  
               public void run() {  
                   try {  
                   } catch (Exception ex) {  
           }, "Self debugging");  
           debugger.setDaemon(true); // Don't hold the VM open  
           // Make sure the debugger has connected  
           if (!startupLatch.await(1, TimeUnit.MINUTES)) {  
               throw new IllegalStateException("Didn't connect before timeout");  
       public void close() throws InterruptedException {  
           shutdown = true;  
           // Somewhere in JDI the interrupt was being eaten, hence the volatile flag   
        * @return A list of exceptions that were thrown 
       public Set<String> getExceptionsViolated() {  
           return new HashSet<String>(set);  
        * Clear the list of exceptions violated 
       public void clearExceptionsViolated() {  
The main connect method is a fairly simple block of code that ensures the connection and configures any initial breakpoints.
       // Implementation details  
       private void connect() throws java.io.IOException {  
           try {  
               // Create a virtual machine connection  
               VirtualMachine attach = connectToVM();  
                   // Add prepare and any already loaded exception breakpoints  
                   // We can now allow the rest of the work to go on as we have created the breakpoints  
                   // we required  
                   // Process the events  
               finally {  
                   // Disconnect the debugger  
                   // Give the debugger time to really disconnect  
                   // before we might reconnect, couldn't find another  
                   // way to do this  
                   try {  
                   } catch (InterruptedException e) {  
           } finally {  
               // Notify watchers that we have shutdown  
Connecting back to self is just a process of finding the right attaching connector, in this case Socket, although I guess you could use the shared memory transport on some platforms if you modified the code slightly.
    private VirtualMachine connectToVM() throws java.io.IOException {  
           List<AttachingConnector> attachingConnectors = Bootstrap.virtualMachineManager().attachingConnectors();  
           AttachingConnector ac = null;  
           for (AttachingConnector next : attachingConnectors) {  
               if (next.name().contains("SocketAttach")) {  
                   ac = next;  
           Map<String, Connector.Argument> arguments = ac.defaultArguments();  
           try {  
               return ac.attach(arguments);  
           } catch (IllegalConnectorArgumentsException e) {  
               throw new IOException("Problem connecting to debugger",e);  
When you connect the debugger you have no idea as to whether the exceptions you are interested in have been loaded, so you need to register breakpoints for both the point where the classes are prepared and for those that have already been loaded.

Note that the breakpoint is set with a policy only to break the one thread, otherwise for obvious reasons the current VM will grind to a halt if the debugger thread is also put to sleep.
    private void createInitialBreakpoints(VirtualMachine attach) {  
           // Our first exception is for class loading  
           for (String exception : exceptions) {  
               ClassPrepareRequest cpr = attach.eventRequestManager().createClassPrepareRequest();  
           // Then we can check each in turn to see if it have already been loaded as we might  
           // be late to the game, remember classes can be loaded more than once  
           for (String exception : exceptions) {  
               List<ReferenceType> types = attach.classesByName(exception);  
               for (ReferenceType type : types) {  
                   createExceptionRequest(attach, type);  
       private static void createExceptionRequest(VirtualMachine attach,   
                                                  ReferenceType refType) {  
           ExceptionRequest er = attach.eventRequestManager().createExceptionRequest(  
               refType, true, true);  
The event processing loop polls for EventSet instances that contain one or more Event instances. Not all of these events are down to a breakpoint request, though, so you have to take care to not always call resume on the event set. This is because you might have two event sets in a row, with the code calling resume before you even get to read the second one. This results in missed breakpoints as the code catches up.

For some reason JDI appeared to be eating the interrupted flag, hence the Boolean property to stop the loop with the close method from before.
    private void processEvents(VirtualMachine attach) {  
           // Listen for events  
           EventQueue eq = attach.eventQueue();  
           eventLoop: while (!Thread.interrupted() && !shutdown) {  
               // Poll for event sets, with a short timeout so that we can  
               // be interrupted if required  
               EventSet eventSet = null;  
                   eventSet = eq.remove(500);  
               catch (InterruptedException ex) {  
                   continue eventLoop;    
               // Just loop again if we have no events  
               if (eventSet == null) {  
                   continue eventLoop;  
               boolean resume = false;  
               for (Event event : eventSet) {  
                   EventRequest request = event.request();  
                   if (request != null) {  
                       int eventPolicy = request.suspendPolicy();  
                       resume |= eventPolicy != EventRequest.SUSPEND_NONE;  
                   if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) {  
                       // This should never happen as the VM will exit before this is called  
                   } else if (event instanceof ClassPrepareEvent) {  
                       // When an instance of the exception class is loaded attach an exception breakpoint  
                       ClassPrepareEvent cpe = (ClassPrepareEvent) event;  
                       ReferenceType refType = cpe.referenceType();  
                       createExceptionRequest(attach, refType);  
                   } else if (event instanceof ExceptionEvent) {  
                       String name = ((ExceptionRequest)event.request()).exception().name();  
               // Dangerous to call resume always because not all event suspend the VM  
               // and events happen asynchornously.  
               if (resume)  
So all that remains is a simple test example, since this is JDK 7 and the ExceptionDebugger is AutoCloseable we can do this using the try-with-resources construct as follows. Obviously, if doing automated tests, use the testing framework fixtures of your choice.
    public class Target {  
       public static void main(String[] args) throws InterruptedException {  
           try (ExceptionDebugger ex = ExceptionDebugger.connect(  
                   NoClassDefFoundError.class.getName())) {  
       private static void doSomeWorkThatQuietlyThrowsAnException() {  
           // Check to see that break point gets fired  
           try {  
               Thread t = new Thread(new Runnable() {  
                               public void run() {  
                                       throw new NoClassDefFoundError();  
                                   catch (Throwable ex) {  
           } catch (Throwable th) {  
               // Eat this and don't tell anybody  
So if you run this class with the following VM parameter, note the suspend=n otherwise the code won't start running, you will find that it can connect back to itself and start running.
This gives you the following output, note the extra debug line from the VM:
Listening for transport dt_socket at address: 5656
Listening for transport dt_socket at address: 5656
As always, I would be interested to read if this was something that is useful for people and to help remove any obvious mistakes.

Sensu: workflow automation for monitoring. Learn more—download the whitepaper.


Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}