Skip to content

Add the Mockito agent properties goal in its own execution - #1186

Merged
MBoegers merged 2 commits into
mainfrom
2968
Aug 7, 2026
Merged

Add the Mockito agent properties goal in its own execution#1186
MBoegers merged 2 commits into
mainfrom
2968

Conversation

@MBoegers

@MBoegers MBoegers commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

The problem

The recipe adds -javaagent:${org.mockito:mockito-core:jar} to Surefire's argLine, and needs the maven-dependency-plugin properties goal to define that property. When a project already declared that plugin, the recipe merged <goal>properties</goal> into an execution the project owns:

<execution>
  <id>copy-dependencies</id>
  <phase>package</phase>          <!-- runs after test -->
  <goals>
    <goal>copy-dependencies</goal>
    <goal>properties</goal>       <!-- merged in -->
  </goals>
</execution>

An execution owns the phase and the configuration of every goal it lists, so the merged goal inherited both. Bound to package, properties runs after Surefire forks, the placeholder is passed through verbatim, and the build fails with:

Error opening zip file or JAR manifest missing : ${org.mockito:mockito-core:jar}
[ERROR] Error occurred during initialization of VM

Two further problems came from the same merge:

  • The XPath .../executions/execution/goals had no positional predicate and the visitor no one-shot guard, so a plugin with N executions got <goal>properties</goal> appended to all N goal lists.
  • PropertiesMojo and AbstractDependencyMojo (the base of copy-dependencies, unpack, analyze, …) both declare a skip parameter. Maven's configuration finalizer matches by parameter name, so an execution carrying <skip> silently skipped the merged goal — the same broken build, with a phase that looks perfectly fine.

The fix

The properties goal now always lands in an execution of its own, never merged into one the project declares:

<execution>
  <id>get-mockito-agent-path</id>
  <goals>
    <goal>properties</goal>
  </goals>
</execution>

No <phase>, so Maven binds each goal of the execution to that goal's own default — initialize for dependency:properties, verified against the mojo descriptor in every published plugin version from 2.4 to 3.11.0.

The explicit <id> is load-bearing rather than cosmetic. Maven merges executions by id — for parent inheritance and for pluginManagement injection alike — and an omitted id is default. Without an id this execution would be captured by any id-less execution declared upstream and handed its <phase>, re-entering the same bug from another direction; and appending an id-less execution next to a project's own id-less one is a duplicate-id model validation error.

The guard is runsPropertiesGoalAtDefaultPhase: we rely on an existing properties goal only when its execution declares no <phase>, because that is the one case where its timing is self-evident. Where a project binds the goal to a phase of its own, deciding whether that lands before the tests would mean reasoning about lifecycle ordering, inheritance and property interpolation, so the goal gets its own execution instead. Worst case that is a second run of a goal which only sets properties; it is never a broken build. The check is mirrored against the document because the resolved model is not refreshed between cycles.

This collapses the four-branch cascade to a single path and retires ChangePluginExecutions and AddOrUpdateChildTag from this recipe. Neither can express the operation: ChangePluginExecutions replaces the whole <executions> element, and AddOrUpdateChildTag is name-keyed and so cannot append a sibling <execution>. Dropping the former also closes a second route to the reported error — it matches groupId with .orElse(null) where AddPluginVisitor defaults the implied Apache group, so a <plugin> declared without a <groupId> used to no-op silently while the argLine was still written.

Tests

Four scenarios added:

  • addsSeparateExecutionWhenExistingExecutionRunsAfterTests — the reported reproduction
  • addsSeparateExecutionWhenPropertiesGoalIsBoundToAPhase — we do not rely on a phase-bound properties goal
  • addsSingleExecutionWhenMavenDependencyPluginHasSeveralExecutions — one execution added regardless of how many exist, with an implied groupId
  • plus the existing suite, where twelve expected outputs changed: nine gained the <id> line, three moved the goal out of the project's execution into its own

Notes for review

The three restructured fixtures did not encode broken behaviour. Their executions are phase-less, and Maven binds each goal of a phase-less execution to that goal's own default, so the previous merge was functionally correct there. This change is a correctness fix for the explicit-late-phase case, plus hardening against configuration inheritance and the N-execution amplification.

One pre-existing gap is untouched and worth a separate look: branch selection reads the resolved model, which includes parent-inherited and active-profile plugins, while the edit is anchored at /project/build/plugins of the current document. A plugin declared only in a parent or only in a profile therefore gets the argLine without the properties goal. It is not the failure reported here, and no test covers it today.

Merging the goal into an execution the project already declares hands it
that execution's phase and configuration. Bound to a phase that runs after
the tests, the agent path is unresolved when Surefire forks.
@github-project-automation github-project-automation Bot moved this to In Progress in OpenRewrite Aug 7, 2026
@MBoegers
MBoegers marked this pull request as draft August 7, 2026 13:28
The additions are independent of the surefire tag edit, so they can be
chained onto the document directly. Ordering is preserved: the dependency
plugin is added before surefire, as the scheduled queue did.
@MBoegers
MBoegers marked this pull request as ready for review August 7, 2026 13:51
@MBoegers
MBoegers requested a review from timtebeek August 7, 2026 13:51
@github-project-automation github-project-automation Bot moved this from In Progress to Ready to Review in OpenRewrite Aug 7, 2026
@MBoegers
MBoegers merged commit 26f898e into main Aug 7, 2026
1 check passed
@MBoegers
MBoegers deleted the 2968 branch August 7, 2026 17:02
@github-project-automation github-project-automation Bot moved this from Ready to Review to Done in OpenRewrite Aug 7, 2026
mergify Bot added a commit to robfrank/linklift that referenced this pull request Aug 20, 2026
…41.0 to 3.42.0 [skip ci]

Bumps [org.openrewrite.recipe:rewrite-migrate-java](https://github.com/openrewrite/rewrite-migrate-java) from 3.41.0 to 3.42.0.
Release notes

*Sourced from [org.openrewrite.recipe:rewrite-migrate-java's releases](https://github.com/openrewrite/rewrite-migrate-java/releases).*

> 3.42.0
> ------
>
> What's Changed
> --------------
>
> * Adopt upstream XML trailing-comment formatting by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-migrate-java#1180](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1180)
> * Add mapping for CheckForNull to JSpecify annotation by [`@​zbynek`](https://github.com/zbynek) in [openrewrite/rewrite-migrate-java#1179](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1179)
> * Skip `UseSetOf`/`UseListOf` for `HashSet`/`ArrayList` subclasses by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-migrate-java#1182](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1182)
> * Avoid hardcoded patch version in UpdateSdkManTest by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-migrate-java#1183](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1183)
> * Only add the Mockito surefire agent configuration when asked, and keep it minimal by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-migrate-java#1184](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1184)
> * Make subpackage recursion explicit in the Jackson JAX-RS JSON rename by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-migrate-java#1185](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1185)
> * Add the Mockito agent properties goal in its own execution by [`@​MBoegers`](https://github.com/MBoegers) in [openrewrite/rewrite-migrate-java#1186](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1186)
> * Derive SDKMAN test versions from the candidate list by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-migrate-java#1188](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1188)
> * Do not apply `var` when the generic return type needs the declared type by [`@​jevanlingen`](https://github.com/jevanlingen) in [openrewrite/rewrite-migrate-java#1187](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1187)
> * Cover legacy Bouncy Castle artifacts and their API changes by [`@​timtebeek`](https://github.com/timtebeek) in [openrewrite/rewrite-migrate-java#1189](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1189)
> * Add jakarta.validation-api dependency when migrating com.sun.istack.NotNull by [`@​steve-aom-elliott`](https://github.com/steve-aom-elliott) in [openrewrite/rewrite-migrate-java#1190](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1190)
>
> New Contributors
> ----------------
>
> * [`@​zbynek`](https://github.com/zbynek) made their first contribution in [openrewrite/rewrite-migrate-java#1179](https://redirect.github.com/openrewrite/rewrite-migrate-java/pull/1179)
>
> **Full Changelog**: <openrewrite/rewrite-migrate-java@v3.41.0...v3.42.0>


Commits

* [`1238ceb`](openrewrite/rewrite-migrate-java@1238ceb) OpenRewrite recipe best practices
* [`01d0fe8`](openrewrite/rewrite-migrate-java@01d0fe8) Add `jakarta.validation-api` dependency when migrating `com.sun.istack.NotNul...
* [`33354b5`](openrewrite/rewrite-migrate-java@33354b5) Cover legacy Bouncy Castle artifacts and their API changes ([#1189](https://redirect.github.com/openrewrite/rewrite-migrate-java/issues/1189))
* [`6c648a5`](openrewrite/rewrite-migrate-java@6c648a5) Update Gradle wrapper to 9.7.0
* [`7099ad3`](openrewrite/rewrite-migrate-java@7099ad3) Do not apply `var` when the generic return type needs the declared type ([#1187](https://redirect.github.com/openrewrite/rewrite-migrate-java/issues/1187))
* [`ccfa7b0`](openrewrite/rewrite-migrate-java@ccfa7b0) Derive SDKMAN test versions from the candidate list ([#1188](https://redirect.github.com/openrewrite/rewrite-migrate-java/issues/1188))
* [`96e8647`](openrewrite/rewrite-migrate-java@96e8647) [Auto] SDKMAN! Java candidates as of 2026-08-10T1102
* [`26f898e`](openrewrite/rewrite-migrate-java@26f898e) Add the Mockito agent properties goal in its own execution ([#1186](https://redirect.github.com/openrewrite/rewrite-migrate-java/issues/1186))
* [`d82dd47`](openrewrite/rewrite-migrate-java@d82dd47) OpenRewrite recipe best practices
* [`55f2e93`](openrewrite/rewrite-migrate-java@55f2e93) Make subpackage recursion explicit in the Jackson JAX-RS JSON rename ([#1185](https://redirect.github.com/openrewrite/rewrite-migrate-java/issues/1185))
* Additional commits viewable in [compare view](openrewrite/rewrite-migrate-java@v3.41.0...v3.42.0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants