How do you extract meaningful information (task path, test identity, outcome, timing) from the events a Tooling API ProgressListener receives?
answer
- getDescriptor() = identity, getResult() = outcome
- TaskOperationDescriptor.getTaskPath()
- JvmTestOperationDescriptor class/method/kind
- TaskSuccessResult.isUpToDate()/isFromCache()
- result.startTime/endTime for duration; parent for tree
basics
~10 sRead event.getDescriptor() for identity (display name, parent, task path or test method) and, on a FinishEvent, event.getResult() for outcome plus start/end times. Cast to the typed subtype (TaskFinishEvent, TestFinishEvent) for category-specific detail.
solid answer
~40 sEvery structured `ProgressEvent` exposes `getEventTime()` and `getDescriptor()`. The descriptor is the operation's identity: `OperationDescriptor` gives `getDisplayName()`, `getName()`, and `getParent()` (so you can build a tree). Typed subtypes add specifics — `TaskOperationDescriptor.getTaskPath()` for `:app:compileJava`, and `JvmTestOperationDescriptor` distinguishing suite/class/method with `getClassName()`/`getMethodName()`. To get outcomes you handle `FinishEvent`, whose `getResult()` returns an `OperationResult` carrying `getStartTime()`/`getEndTime()` and the success/failure state. For tasks, the result is a `TaskExecutionResult` subtype: `TaskSuccessResult` (with `isUpToDate()`, `isFromCache()`), `TaskFailureResult` (with `getFailures()`), or `TaskSkippedResult`. For tests, `TestSuccessResult`/`TestFailureResult`/`TestSkippedResult`. So the pattern is: switch on event subtype, cast the descriptor for identity, and on finish inspect the result for outcome and timing. Parent links let you reconstruct the hierarchy the IDE renders.
code
kotlin · 14 linesProgressListener { event ->
if (event is TaskFinishEvent) {
val d = event.descriptor as TaskOperationDescriptor
val r = event.result
val durationMs = r.endTime - r.startTime
val tag = when (r) {
is TaskSuccessResult -> if (r.isFromCache) "FROM-CACHE" else if (r.isUpToDate) "UP-TO-DATE" else "EXECUTED"
is TaskSkippedResult -> r.skipMessage
is TaskFailureResult -> "FAILED: " + r.failures.firstOrNull()?.message
else -> "?"
}
println("${'$'}{d.taskPath} -> ${'$'}tag (${'$'}durationMs ms)")
}
}go deeper
Know getDescriptor() gives the operation name and FinishEvent has a result with the outcome.
Cast to typed descriptors for taskPath/test identity and read success/failure/skipped results.
Distinguish up-to-date vs from-cache vs executed, extract failures, and rebuild the tree via getParent().
Design a normalized event model mapping descriptors/results to the IDE's domain across operation categories and Gradle versions.
## The two halves of every event A structured `ProgressEvent` always answers two questions: 1. **Who?** — `getDescriptor()` returns an `OperationDescriptor`. 2. **When?** — `getEventTime()` (epoch millis of the event). Finish events add **what happened** via `getResult()`. ## Descriptors: identity and hierarchy `OperationDescriptor` (base) gives: - `getDisplayName()` — human label ("Task :app:test"). - `getName()` — short name. - `getParent()` — the enclosing operation's descriptor, or null. This is how an IDE builds the **tree**: walk parents to nest tasks under projects, tests under suites. Typed descriptors specialize: - **`TaskOperationDescriptor`** → `getTaskPath()` (e.g. `:lib:compileKotlin`). - **`JvmTestOperationDescriptor`** → `getJvmTestKind()` (suite / class / method), `getClassName()`, `getMethodName()`, `getSuiteName()`. - **`ProjectConfigurationOperationDescriptor`**, `TransformOperationDescriptor`, `FileDownloadOperationDescriptor`, etc. ## Results: outcome and timing Only `FinishEvent` has a result. The base `OperationResult` provides `getStartTime()` and `getEndTime()` (compute duration from these). Subtypes encode the outcome: - Generic: `SuccessResult` / `FailureResult` (`getFailures()` → list of `Failure`). - **Task** results (`TaskExecutionResult` family): - `TaskSuccessResult` — `isUpToDate()`, `isFromCache()`, `isIncremental()`. - `TaskFailureResult` — `getFailures()`. - `TaskSkippedResult` — `getSkipMessage()` (e.g. NO-SOURCE). - **Test** results: `TestSuccessResult`, `TestFailureResult` (failure exceptions), `TestSkippedResult`. ## Putting it together ```kotlin ProgressListener { event -> when (event) { is TaskFinishEvent -> { val path = (event.descriptor as TaskOperationDescriptor).taskPath val ms = event.result.endTime - event.result.startTime val outcome = when (val r = event.result) { is TaskSuccessResult -> if (r.isFromCache) "FROM-CACHE" else if (r.isUpToDate) "UP-TO-DATE" else "OK" is TaskSkippedResult -> r.skipMessage is TaskFailureResult -> "FAILED" else -> "?" } record(path, outcome, ms) } is TestFinishEvent -> { val d = event.descriptor as JvmTestOperationDescriptor reportTest(d.className, d.methodName, event.result) } } } ``` ## Practical notes - Compute durations from result start/end, not from `eventTime` deltas — the result times are authoritative for the operation. - Use `getParent()` to attach a node to the right place in the tree; root operations have a null parent. - Failures expose a `Failure` chain (message, description, causes) you can surface to the user.
- How do you know a task was served from the build cache rather than freshly executed?Cast the TaskFinishEvent's result to TaskSuccessResult and check isFromCache(); isUpToDate() distinguishes incremental up-to-date from actually executed.
- How do you reconstruct the build/test tree the IDE shows?Use OperationDescriptor.getParent() to nest each operation under its enclosing one — tasks under projects, test methods under classes under suites; root operations have a null parent.
- Where do you get the duration of an operation?From the FinishEvent's OperationResult: endTime minus startTime. These are authoritative for the operation, unlike event-time deltas.
saying these in an interview costs you the question
- Trying to read a result off a StartEvent — only FinishEvent carries getResult().
- Computing duration from eventTime instead of result start/end times.
- Ignoring TaskSuccessResult.isFromCache/isUpToDate and reporting every success as 'executed'.