skip to content

How does an IDE debug a single test through the Tooling API? Explain debugTestsOn(port) and what happens under the hood.

level: middleimportance: should knowfreq 35%

answer

  1. debugTestsOn(port) on TestLauncher
  2. JDWP agent on the forked test worker
  3. IDE listens; worker attaches back
  4. transport=dt_socket
  5. attach to worker, not daemon

basics

~10 s

TestLauncher.debugTestsOn(port) starts the test JVM with a JDWP debug agent that connects back to the IDE's debugger listening on that port. The IDE's breakpoints then hit in the test process.

solid answer

~50 s

`debugTestsOn(int port)` tells `TestLauncher` to launch the forked **test JVM** in debug mode. Under the hood Gradle adds JDWP (Java Debug Wire Protocol) agent JVM arguments to the test worker so it acts as a debug client connecting back to the IDE. Crucially, the IDE first opens a debug **listener** (server) on `port`, then calls `debugTestsOn(port)`; the test JVM is started with `-agentlib:jdwp=transport=dt_socket,server=n,address=<port>` and dials into the IDE. Once attached, the developer's breakpoints in that test (and the code it exercises) suspend the test worker. This is exactly how 'Debug single test' works in IntelliJ/Eclipse via Gradle. The IDE typically combines it with `withJvmTestMethods` to debug one method, plus progress listeners to update the test tree. Note the test runs in a forked worker JVM, not the daemon, so it's that worker you attach to.

code

kotlin · 6 lines
kotlin
val debugPort = 5005 // IDE opened a JDWP listener here first
connection.newTestLauncher()
    .withJvmTestMethods("com.acme.OrderTest", "placesOrder")
    .debugTestsOn(debugPort)
    .setStandardOutput(System.out)
    .run()

go deeper

for a junior

Know debugTestsOn(port) lets the IDE debug a single test by attaching to the test JVM.

for a middle

Explain JDWP, the forked worker, and that the IDE listens while the worker connects back.

for a senior

Distinguish test-worker debugging from daemon debugging and handle single-worker determinism.

for a principal

Define a robust IDE debug protocol: port allocation, listener lifecycle, and avoiding parallel-worker nondeterminism.

## What 'debugging a test' means here Tests run in a **forked JVM** (the Gradle 'test worker'), separate from both the IDE and the Gradle daemon. To debug, the IDE's debugger and that worker JVM must establish a JDWP connection. ## JDWP roles: server vs client JDWP can run in two directions: - **listen/server mode** — the debuggee waits for a debugger to attach. - **attach/client mode** — the debuggee connects out to a debugger that is already listening. The Tooling API's `debugTestsOn(port)` uses the **attach** direction: the IDE opens a *listening* debug server on `port` first, then the test worker JVM is launched with a JDWP agent in client mode and connects back to that port. (Historically there's also an overload accepting `DebugOptions` to tune this, but the common form is `debugTestsOn(port)`.) ## The flow, step by step 1. IDE starts a debug listener on, say, `localhost:5005`. 2. IDE creates a `TestLauncher`, selects the test (`withJvmTestMethods(...)`), and calls `debugTestsOn(5005)`. 3. Gradle starts the test worker with JDWP arguments pointing at `5005`. 4. The worker connects to the IDE; breakpoints become live. 5. The developer steps through the test and the code under test in the worker JVM. ```kotlin val debugPort = 5005 // IDE already listening here connection.newTestLauncher() .withJvmTestMethods("com.acme.OrderTest", "placesOrder") .debugTestsOn(debugPort) .run() ``` ## Why this matters / common confusion - You attach to the **test worker**, not the daemon. Setting `org.gradle.debug=true` debugs the *build/daemon*, which is a different thing. - Forking and parallel test workers: when debugging, you generally want a single worker so the breakpoint is deterministic. - The port must be free and the IDE must be listening before `run()` connects; otherwise the worker fails to attach. ## Relation to selection Debugging is orthogonal to selection — you still pick tests with `withJvmTestClasses`/`withJvmTestMethods`. `debugTestsOn` just changes *how* the worker JVM launches.

  • Which JVM does the debugger attach to — the daemon or something else?
    The forked test worker JVM that actually runs the tests, not the Gradle daemon. debugTestsOn injects JDWP args into that worker.
  • Who listens and who connects in the debugTestsOn handshake?
    The IDE opens a listening debug server on the port first; the launched test worker connects out to it (JDWP client/attach mode).
  • How is this different from setting org.gradle.debug=true?
    org.gradle.debug debugs the build process/daemon itself; debugTestsOn debugs the test worker JVM. They target different processes.

The IDE leaves a phone line open (the listening port); debugTestsOn tells the test JVM to dial that exact number when it starts, so the debugger can listen in on the call.

saying these in an interview costs you the question

  • Saying you attach to the Gradle daemon — you attach to the test worker.
  • Confusing debugTestsOn with org.gradle.debug (which debugs the build).
  • Getting the JDWP direction backwards — the IDE listens, the worker attaches.

context