Skip to content

Commit 13f80a4

Browse files
committed
wip
1 parent 103e236 commit 13f80a4

1 file changed

Lines changed: 238 additions & 2 deletions

File tree

Lines changed: 238 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
---
22
name: debug-surefire
3-
description: Debug Maven Surefire unit tests by running them in JDWP "wait for debugger" mode (`-Dmaven.surefire.debug`) and attaching IntelliJ/VS Code/jdb. Use when asked to debug/step through a failing JUnit test, attach a debugger to a Maven test run, or run `mvn test -Dtest=Class[#method]` suspended on a port (including multi-module `-pl` runs).
3+
description: Debug Maven Surefire unit tests by running them in JDWP "wait for debugger" mode (`-Dmaven.surefire.debug`) and attaching to the forked test JVM using **jdb** (preferred for CLI/agent debugging), IntelliJ, or VS Code. Use when asked to debug/step through a failing JUnit test, attach a debugger to a Maven test run, or run `mvn test -Dtest=Class[#method]` suspended on a port (including multi-module `-pl` runs). The JVM will block at startup until a debugger attaches; the agent should attach with `jdb -attach <host>:<port>` and drive the session from the terminal.
44
---
55

66
# debug-surefire
77

8-
Run Maven Surefire tests suspended in JDWP so you can attach a debugger and step through the code.
8+
Run Maven Surefire tests **suspended in JDWP** so you can attach a debugger and step through the code.
9+
In headless/agent environments, **attach with `jdb`** (the JDK’s command-line debugger).
10+
11+
## What this does
12+
13+
- Runs `mvn test` with Surefire in debug mode via `-Dmaven.surefire.debug`.
14+
- Surefire launches the **forked test JVM** with a JDWP socket and `suspend=y`, meaning:
15+
- the forked JVM starts,
16+
- prints “Listening for transport dt_socket at address: <port>”,
17+
- then **waits** until a debugger attaches,
18+
- only then does it begin executing tests.
19+
20+
This is important: you do **not** attach to the `mvn` process itself; you attach to the **forked JVM** running the tests.
921

1022
## Quick start
1123

24+
1) Start a suspended test run
25+
1226
- Debug a test class:
1327
- `.codex/skills/debug-surefire/scripts/debug-surefire.sh --test-class MyTest`
1428
- Debug a single test method (quote the `#`):
@@ -17,10 +31,232 @@ Run Maven Surefire tests suspended in JDWP so you can attach a debugger and step
1731
- `.codex/skills/debug-surefire/scripts/debug-surefire.sh --module core/sail/shacl --test-class ShaclSailTest`
1832
- `.codex/skills/debug-surefire/scripts/debug-surefire.sh --module rdf4j-sail-shacl --test 'ShaclSailTest#testSomething'`
1933

34+
When the forked JVM is ready, you should see something like:
35+
36+
- `Listening for transport dt_socket at address: 55005`
37+
38+
2) Attach with `jdb` (preferred)
39+
40+
In a second terminal, attach to the printed port:
41+
42+
- Local attach (port only implies localhost):
43+
- `jdb -attach 55005`
44+
- Explicit host+port:
45+
- `jdb -attach localhost:55005`
46+
47+
If you need source listing inside jdb, provide a sourcepath up front:
48+
49+
- `jdb -sourcepath module/src/main/java:module/src/test/java -attach 55005`
50+
51+
3) Set breakpoints / catches, then resume
52+
53+
Once you’re at the `jdb` prompt, set a breakpoint (or exception catch) and continue:
54+
55+
- `stop in com.example.MyTest.shouldDoThing`
56+
- `catch uncaught java.lang.AssertionError`
57+
- `cont`
58+
59+
Tip: right after attaching to a just-started suspended JVM, `jdb` may show:
60+
- `No frames on the current call stack`
61+
62+
That’s normal. The JVM hasn’t executed into any Java frames yet. Set breakpoints first, then `cont`.
63+
64+
---
65+
2066
## Notes
2167

2268
- The script runs a fast pre-test install (`-Pquick` into the repo-local `.m2_repo`) and then runs `mvn test` with Surefire in debug mode.
2369
- Use `SUREFIRE_DEBUG_PORT=8000` to change the port (default: `55005`).
2470
- Use `--no-offline` / `--online` if offline (`-o`) resolution fails.
2571
- Everything after `--` is passed to Maven, e.g.:
2672
- `.codex/skills/debug-surefire/scripts/debug-surefire.sh --test-class MyTest -- -DtrimStackTrace=false -DfailIfNoTests=false -DforkCount=1 -DreuseForks=false`
73+
74+
---
75+
76+
## jdb interaction guide
77+
78+
This section is optimized for quickly getting signal from a failing unit test without drowning in framework/JDK internals.
79+
80+
### Command cheat sheet (the ones you’ll actually use)
81+
82+
**Start / resume**
83+
- `cont` — continue execution from current stop/breakpoint
84+
- `run` — start execution *when jdb launched the VM* (less relevant when you used `-attach`)
85+
86+
**Breakpoints**
87+
- `stop in <class>.<method>` — break on method entry
88+
- `stop at <class>:<line>` — break at a specific line
89+
- `stop` — list all breakpoints
90+
- `clear <class>.<method>` / `clear <class>:<line>` — remove a breakpoint
91+
- `clear` — list breakpoints (same idea as `stop` listing)
92+
93+
**Stepping**
94+
- `next` — step over calls (line-level)
95+
- `step` — step into calls (line-level)
96+
- `step up` — run until current method returns
97+
- `stepi` — step one bytecode instruction (rarely needed)
98+
99+
**Threads / stacks**
100+
- `threads` — list threads
101+
- `thread <id>` — select default thread
102+
- `where` — stack trace for current thread
103+
- `where all` — stack traces for all threads
104+
- `up` / `down` — move the current frame up/down the stack
105+
106+
**Inspect state**
107+
- `locals` — print locals in current frame
108+
- `print <expr>` — evaluate/print an expression (same as `eval`)
109+
- `dump <expr>` — more complete object dump
110+
- `set <lvalue> = <expr>` — mutate a variable/field (use sparingly)
111+
112+
**Source**
113+
- `list` — show source around current line
114+
- `list <line>` or `list <method>` — show a specific region
115+
- `use <path1>:<path2>` (aka `sourcepath`) — set where jdb looks for sources
116+
117+
**Exceptions**
118+
- `catch uncaught <exception>` — break when an uncaught exception occurs
119+
- `catch caught <exception>` — break when a caught exception occurs
120+
- `catch all <exception>` — break for both caught+uncaught
121+
- `ignore ...` — undo a catch
122+
123+
**Reduce noise**
124+
- `exclude <pattern>,<pattern>,...` — don’t report step/method events for matching classes
125+
- `exclude none` — clear exclusions
126+
127+
**Automation**
128+
- `monitor <command>` — run a command every time the program stops (e.g., `monitor where`)
129+
- `read <file>` — execute commands from a file
130+
- `!!` — repeat last command
131+
- `<n> <command>` — repeat command n times (e.g., `10 next`)
132+
133+
### Efficient workflow for failing JUnit tests
134+
135+
#### 1) Add exclusions immediately (so stepping doesn’t become a swamp)
136+
137+
Right after connecting, set exclusions to avoid stepping into JDK/framework code:
138+
139+
- `exclude java.*,javax.*,jdk.*,sun.*,com.sun.*,org.junit.*,org.junit.jupiter.*,org.assertj.*,org.hamcrest.*,org.mockito.*,org.apache.maven.*`
140+
141+
You can always clear this later:
142+
143+
- `exclude none`
144+
145+
#### 2) Break where it matters
146+
147+
Common breakpoint patterns:
148+
149+
- Break at the failing test method:
150+
- `stop in com.example.MyTest.shouldDoThing`
151+
152+
- Break inside the code under test:
153+
- `stop in com.example.service.FooService.doWork`
154+
155+
- Break at a specific suspicious line:
156+
- `stop at com.example.service.FooService:123`
157+
158+
If the method is overloaded, specify argument types:
159+
160+
- `stop in com.example.FooService.doWork(int,java.lang.String)`
161+
162+
Note: `jdb` supports **deferred breakpoints**. If the class isn’t loaded yet, it will still accept the breakpoint and activate it when the class loads.
163+
164+
#### 3) Break on the *failure*, not just your guess
165+
166+
For unit tests, breaking on the thrown assertion/error is often faster than guessing a line.
167+
168+
Useful catches:
169+
170+
- Classic Java assertions / many test failures:
171+
- `catch uncaught java.lang.AssertionError`
172+
173+
- Common in JUnit 5 assertions:
174+
- `catch uncaught org.opentest4j.AssertionFailedError`
175+
176+
- NPE hunting:
177+
- `catch uncaught java.lang.NullPointerException`
178+
179+
You can remove a catch with `ignore`:
180+
181+
- `ignore uncaught java.lang.AssertionError`
182+
183+
Tip: `catch all java.lang.Throwable` is the nuclear option. It works, but it can get loud.
184+
185+
#### 4) Resume and drive
186+
187+
Once breakpoints/catches are set:
188+
189+
- `cont`
190+
191+
When you hit a breakpoint:
192+
193+
- `where` to see the call stack
194+
- `list` to see nearby source
195+
- `locals` to see local variables
196+
- `print someVar` or `print this.someField` to inspect
197+
- `next` to step over, `step` to step into
198+
199+
#### 5) Find the “real” test thread quickly
200+
201+
Surefire + JUnit can spin up multiple threads (and Maven itself has its own). When in doubt:
202+
203+
- `threads`
204+
- `where all`
205+
206+
Then pick the thread that’s in your test/code-under-test:
207+
208+
- `thread <id>`
209+
- `where`
210+
211+
#### 6) Keep the JVM count predictable
212+
213+
Surefire forking and parallelism can make debugging confusing. If you see multiple JVMs / inconsistent behavior, pass Maven flags to keep it single and fresh:
214+
215+
- `-DforkCount=1 -DreuseForks=false`
216+
217+
Example:
218+
219+
- `.codex/skills/debug-surefire/scripts/debug-surefire.sh --test 'MyTest#shouldDoThing' -- -DforkCount=1 -DreuseForks=false`
220+
221+
#### 7) Use “thread-only” breakpoints when concurrency matters
222+
223+
By default, `jdb` breakpoints suspend all threads, which can create deadlocks in concurrent tests. You can tell jdb to suspend only the thread that hits the breakpoint:
224+
225+
- `stop thread in com.example.concurrent.Worker.run`
226+
227+
(You can also target a specific thread id with `stop thread <thread_id> in ...` if needed.)
228+
229+
---
230+
231+
## jdb startup customization (optional but powerful)
232+
233+
jdb will execute startup commands from `jdb.ini` or `.jdbrc` in either `user.home` or `user.dir`.
234+
235+
This is handy for keeping your default exclusions/catches consistent, e.g.:
236+
237+
- `exclude java.*,javax.*,jdk.*,sun.*,com.sun.*,org.junit.*,org.assertj.*`
238+
- `catch uncaught java.lang.AssertionError`
239+
240+
You can also keep a project-local command file and load it on demand:
241+
242+
- `read .codex/skills/debug-surefire/jdb.cmds`
243+
244+
---
245+
246+
## Troubleshooting
247+
248+
- **jdb can’t connect**
249+
- Confirm the port printed by the suspended JVM matches what you used in `jdb -attach`.
250+
- Confirm you’re attaching to the forked JVM port (the one that prints “Listening for transport…”), not Maven’s PID.
251+
- If running remotely (CI box / container / VM), ensure the port is reachable or forwarded.
252+
253+
- **Breakpoints don’t hit**
254+
- Use fully qualified class names.
255+
- If overloaded, include argument types.
256+
- Use `classes` to see what’s loaded.
257+
- Try `stop at <class>:<line>` to avoid signature mismatch.
258+
259+
- **`list` can’t find source**
260+
- Set sourcepath:
261+
- `use module/src/main/java:module/src/test/java`
262+
- Or launch jdb with `-sourcepath ...` from the start.

0 commit comments

Comments
 (0)