skip to content

How do you extract meaningful information (task path, test identity, outcome, timing) from the events a Tooling API ProgressListener receives?

level: seniorimportance: should knowfreq 20%

answer

  1. getDescriptor() = identity, getResult() = outcome
  2. TaskOperationDescriptor.getTaskPath()
  3. JvmTestOperationDescriptor class/method/kind
  4. TaskSuccessResult.isUpToDate()/isFromCache()
  5. result.startTime/endTime for duration; parent for tree

basics

~10 s

Read 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 s

Every 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 lines
kotlin
ProgressListener { 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

for a junior

Know getDescriptor() gives the operation name and FinishEvent has a result with the outcome.

for a middle

Cast to typed descriptors for taskPath/test identity and read success/failure/skipped results.

for a senior

Distinguish up-to-date vs from-cache vs executed, extract failures, and rebuild the tree via getParent().

for a principal

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'.

context