Thursday, December 16, 2010

Breakpoint dependencies in Eclipse

This post describes a hack to achieve the following behavior in the Eclipse debugger: stop at a given breakpoint only if another breakpoint has been reached before.
I'm not aware of an existing solution to do that (although IDEA seems to have that functionality) — and if I'm wrong, I've just lost a couple of hours of my life :-)

The problem


Suppose I'm debugging a unit test that runs the same assertion over and over with different parameters (I know unit tests should have few assertions, but sometimes this pattern is just more practical):
  assertEquals(43, SomeClass.someMethod(1));
  assertEquals(44, SomeClass.someMethod(2));
  assertEquals(45, SomeClass.someMethod(3));
  assertEquals(50, SomeClass.someMethod(4));
Suppose the last assertion is failing. I want to debug it, and I'm particularly interested in an internal method called indirectly by someMethod, deep down the call stack:
private static int internalMethod(int p) {
  return p + 42;
}
Basically, I have three options:
  • set a breakpoint on internalMethod. But the debugger will suspend every time the method is reached, including in the first N-1 calls to someMethod that I'm not interested in. That sucks.
  • set the breakpoint on the assertion that fails. But when the debugger suspends, I still have to go down the call stack to reach internalMethod. This involves using Go into repeatedly, which may go into a lot of auxiliary code I'm not interested in (any method invoked to build parameters, class loads, etc.). That also sucks.
  • set the breakpoint on the outer method, and when it suspends, set the breakpoint on the inner method manually, and hit Continue to reach it directly. That sucks less, but still involves manual work (to be undone and redone everytime I rerun the test).
Wouldn't it be nice to set both breakpoints, and configure the second one to "activate" only after the first one has been reached? Well that's possible: breakpoints can be conditioned by Java code, and that code can have side effects.

The solution


I drop a class named Breaks in my test folder (the source code of the class is available here). On the outer breakpoint, I add the following condition:



This sets a "marker" that is accessible from other breakpoints. The set method returns false, so the debugger will never suspend on that breakpoint (if I want it to, there is an equivalent setAndBreak method).

On the second breakpoint, the condition invokes a method that returns true only if the marker is set (alternatively, testAndClear also deletes the marker):



VoilĂ ! As long as the outer breakpoint hasn't been reached, the inner breakpoint will not suspend.

This is portable to any debugger that supports conditions, and more elaborate strategies are possible (for instance, associating counters to the markers, instead of simple on / off state).

Update 2010/12/18: you will run into compile errors if Breaks and the debugged classes reside in different packages. To avoid this, put Breaks in a package with a short name, and use the fully qualified name in the breakpoint conditions (putting it in the default package doesn't seem to work, and import statements are not allowed in breakpoint conditions).

0 comments: