Skip to content

Improve "Delegate Test to Gradle" to a default-level test experience (coverage, discoverability, latency) #1890

Description

@wenytang-ms

Summary

Improve Delegate Test to Gradle into a complete and reliable test experience for BSP Gradle projects. The first step remains an explicit, non-default Delegate Test to Gradle (Coverage) profile so users can opt in and we can evaluate adoption and reliability before considering any project-level default change.

Motivation

Two vscode-java-test issues share the same execution-path problem:

The default Java Test runner launches a standalone test JVM from the JDT model. It does not execute Gradle's Test task, so Gradle task dependencies, processed resources, JVM arguments, environment and other task configuration may be missing.

Delegate Test avoids this by keeping Gradle as the execution authority:

vscode-java-test
    -> vscode-gradle delegate runner
    -> BSP buildTarget/test
    -> Gradle TestLauncher / Test task

This is why delegated Run and Debug already avoid #1880 and #1878. Coverage should preserve the same execution path.

Design principles

  1. Gradle owns test execution. Coverage must continue to use BSP buildTarget/test and the real Gradle Test task.
  2. Java Test owns coverage presentation and analysis. Source/class mapping, JaCoCo analysis and VS Code Coverage objects should remain in vscode-java-test/JDTLS.
  3. Exchange artifacts, not implementation APIs. The Gradle runner should produce isolated JaCoCo execution data; it should not call an exposed Java Test coverage-analysis API.
  4. Do not modify user report tasks. Delegated coverage must not reconfigure or execute the user's jacocoTestReport task.
  5. Start opt-in. Add an explicit user-selectable profile and observe usage/reliability before changing defaults.

Proposed architecture

Execution and artifact ownership

vscode-java-test creates a unique execution-data directory for each Coverage run and passes it through the existing external TestRunner context using an optional coverage artifact descriptor, for example:

interface IRunTestContext {
    coverage?: {
        format: "jacoco-exec";
        outputDirectory: vscode.Uri;
    };
}

The exact API shape is subject to review, but the ownership should be:

vscode-java-test
    creates run-specific exec directory
        -> vscode-gradle
           runs BSP buildTarget/test
           configures Gradle Test tasks to write isolated *.exec files
        -> vscode-java-test/JDTLS
           loads all *.exec files
           maps class outputs to source roots from IJavaProject
           publishes VS Code FileCoverage
        -> vscode-java-test cleans the run directory

Gradle runner responsibilities

  • Execute tests through BSP buildTarget/test; retain the task-server path only as fallback when BSP is unavailable.
  • Use a per-run init script only to attach/configure JaCoCo on the forked Gradle Test JVM and redirect execution data into the provided directory.
  • Keep execution data isolated by run, Gradle project and Test task.
  • Wait for Gradle completion before signaling that coverage artifacts are ready.
  • Avoid generating JaCoCo XML and avoid configuring, finalizing or explicitly running jacocoTestReport.

Java Test/JDTLS responsibilities

Extend the existing default Coverage implementation instead of creating a second analyzer:

  • JavaTestCoverageProvider remains internal to vscode-java-test; it does not need to be exposed to vscode-gradle.
  • After an external Coverage runner finishes, the Java Test controller invokes its own provider.
  • CoverageHandler accepts an execution-data directory or file list and repeatedly calls ExecFileLoader.load(...) to merge all .exec files.
  • Continue using the imported IJavaProject model (rawClasspath, source entries, output locations and required projects) for class/source mapping.
  • Keep JaCoCo Agent and Analyzer versions compatible and owned by the Java Test integration contract.
  • Publish line, branch and method coverage through the existing VS Code Coverage API.

This reuses the default Coverage path without reusing the default test launcher. Tests still execute through Gradle, so the design does not reintroduce #1880 or #1878.

Why not generate JaCoCo XML in vscode-gradle?

Generating XML in Gradle requires configuring and executing a report task, then duplicating source resolution and XML-to-VS-Code conversion in TypeScript. It also risks changing user build behavior when a build already defines custom jacocoTestReport dependencies, actions or outputs.

Using .exec as the boundary:

  • avoids modifying user report tasks;
  • reuses the existing JDTLS coverage analyzer;
  • removes duplicate XML parsing and source-root matching;
  • preserves method coverage;
  • may remove the current Gradle 6.1 report-DSL requirement, subject to compatibility testing.

UX and onboarding

P0 should add explicit profiles:

  • Delegate Test to Gradle
  • Delegate Test to Gradle (Debug)
  • Delegate Test to Gradle (Coverage)

Do not automatically retry a failed standard run with Gradle. A failure followed by an unexplained automatic rerun is surprising and makes execution behavior difficult to understand. Users should deliberately select the Gradle profile.

Existing java.test.runTests telemetry includes the selected profile label and can measure opt-in adoption. Reliability telemetry should additionally distinguish BSP execution, task-server fallback, cancellation and coverage-analysis failures.

Phased plan

P0 — Opt-in delegated Coverage

vscode-java-test

  • Extend the external TestRunner contract with a run-scoped Coverage artifact location.
  • Invoke the internal Coverage provider after a delegated Coverage run completes.
  • Allow CoverageHandler to load multiple .exec files.
  • Own artifact cleanup and detailed Coverage registration.

vscode-gradle

  • Register Delegate Test to Gradle (Coverage).
  • Keep BSP-first Gradle Test execution.
  • Produce isolated .exec files through the Gradle Test tasks.
  • Do not generate XML or interact with user JaCoCo report tasks.

Validation

  • Standard single-project Java build.
  • Multi-project and multiple Test tasks.
  • Existing user JaCoCo configuration.
  • Parallel forks and failed tests with partial execution data.
  • BSP and task-server fallback.
  • BSP/Buildship IJavaProject output locations matching Gradle class outputs.
  • Cancellation and cleanup while preventing reads of files still being written.

P1 — Reliability and performance

  • Thread BSP originId through test callbacks so runs can be correlated without global serialization.
  • Reuse long-lived build-server/Gradle daemon state and avoid unnecessary clean tasks.
  • Improve debug handling for multiple Test tasks/forks.
  • Validate custom source sets, generated sources and composite builds.

P2 — Evaluate default behavior

Use adoption, reliability, latency and failure telemetry from the explicit profiles to decide whether BSP Gradle projects should use delegation by default. Preserve a clearly named standard-JVM profile as an escape hatch if the default changes.

Current gaps

Gap Impact
Coverage artifact handoff is not part of the TestRunner contract Java Test cannot yet automatically analyze delegated execution data
CoverageHandler expects one fixed jacoco.exec It cannot safely consume per-project/per-task isolated files
BSP callbacks do not include a usable run identifier Delegated runs must currently be serialized
Complex Gradle source/output models need validation Coverage may be incomplete for custom source sets or composite builds
First-run Gradle latency Slower feedback than the standalone JDT launcher for small test loops

Cross-repository work

  • vscode-java-test: Coverage artifact contract, multi-exec analysis and controller-owned Coverage lifecycle.
  • vscode-gradle: BSP execution and isolated execution-data production.
  • build-server-for-gradle: no Coverage-specific endpoint is required for P0; originId propagation remains a useful follow-up for concurrency and routing.

This issue tracks the complete delegated test experience motivated by microsoft/vscode-java-test#1880 and #1878. PR #1893 is the initial Coverage implementation and should align with the artifact-based design above.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions