From 3231ca59863a9dcb1d98799b141925cd729f527e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Fri, 7 Aug 2026 15:24:55 +0200 Subject: [PATCH 1/2] Add the Mockito agent properties goal in its own execution 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. --- ...MockitoJavaAgentToMavenSurefirePlugin.java | 90 ++--- .../resources/META-INF/rewrite/examples.yml | 1 + ...itoJavaAgentToMavenSurefirePluginTest.java | 373 ++++++++++++++++++ 3 files changed, 417 insertions(+), 47 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java b/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java index 4f068ce090..de712041ba 100644 --- a/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java +++ b/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java @@ -16,7 +16,6 @@ package org.openrewrite.java.migrate; import lombok.Getter; -import lombok.RequiredArgsConstructor; import org.intellij.lang.annotations.Language; import org.jspecify.annotations.Nullable; import org.openrewrite.ExecutionContext; @@ -27,15 +26,14 @@ import org.openrewrite.internal.StringUtils; import org.openrewrite.maven.AddPlugin; import org.openrewrite.maven.AddPropertyVisitor; -import org.openrewrite.maven.ChangePluginExecutions; import org.openrewrite.maven.MavenIsoVisitor; import org.openrewrite.maven.search.DependencyInsight; import org.openrewrite.maven.search.FindPlugin; +import org.openrewrite.maven.trait.MavenPlugin; import org.openrewrite.maven.tree.Plugin; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; import org.openrewrite.semver.LatestRelease; -import org.openrewrite.xml.AddOrUpdateChildTag; import org.openrewrite.xml.AddToTagVisitor; import org.openrewrite.xml.XPathMatcher; import org.openrewrite.xml.XmlIsoVisitor; @@ -54,14 +52,11 @@ public class AddMockitoJavaAgentToMavenSurefirePlugin extends Recipe { private static final String MAVEN_SUREFIRE_PLUGIN_ARTIFACT_ID = "maven-surefire-plugin"; private static final String MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID = "maven-dependency-plugin"; - @Language("xpath") - private static final String MAVEN_DEPENDENCY_PLUGIN_EXECUTION_MATCHER = "/project/build/plugins/plugin[artifactId='maven-dependency-plugin']/executions/execution"; + private static final String PROPERTIES_GOAL = "properties"; @Language("xml") - private static final String MAVEN_DEPENDENCY_PLUGIN_PROPERTIES_GOAL = "properties"; - - @Language("xml") - private static final String MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG = ""+ MAVEN_DEPENDENCY_PLUGIN_PROPERTIES_GOAL + ""; + private static final String MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG = + "get-mockito-agent-path" + PROPERTIES_GOAL + ""; @Getter final String displayName = "Add Mockito Java Agent to Maven Surefire Plugin"; @@ -105,24 +100,6 @@ private boolean usesRuntimeArgLineProperty() { declaresJacocoPlugin(getCursor().firstEnclosingOrThrow(Xml.Document.class)); } - private void maybeAddMavenDependencyPluginWithPropertiesGoal() { - Optional mavenDependencyPlugin = getResolutionResult().getPom().getPlugins().stream() - .filter(plugin -> isMavenPlugin(plugin, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID)).findFirst(); - - if (!mavenDependencyPlugin.isPresent()) { - doAfterVisit(new AddPlugin(MAVEN_PLUGINS_GROUP_ID, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID, null, null, null, - "" + MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG + "", "**/pom.xml").getVisitor()); - } else if (mavenDependencyPlugin.get().getExecutions().isEmpty()) { - doAfterVisit(new ChangePluginExecutions(MAVEN_PLUGINS_GROUP_ID, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID, MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG).getVisitor()); - } else if (mavenDependencyPlugin.get().getExecutions().stream().noneMatch(execution -> execution.getGoals() != null)) { - doAfterVisit(new AddOrUpdateChildTag(MAVEN_DEPENDENCY_PLUGIN_EXECUTION_MATCHER, "" + MAVEN_DEPENDENCY_PLUGIN_PROPERTIES_GOAL + "", false).getVisitor()); - } else if (!hasPropertiesGoal(mavenDependencyPlugin.get())) { - doAfterVisit(new AppendChildTagToParentVisitor( - new XPathMatcher(MAVEN_DEPENDENCY_PLUGIN_EXECUTION_MATCHER + "/goals"), - Xml.Tag.build(MAVEN_DEPENDENCY_PLUGIN_PROPERTIES_GOAL))); - } - } - private @Nullable String surefireArgLineWithAgent(List plugins) { String agentArgument = getArgLineJavaAgentArgument(); return plugins.stream() @@ -132,11 +109,6 @@ private void maybeAddMavenDependencyPluginWithPropertiesGoal() { .findFirst().orElse(null); } - private boolean mavenDependencyPluginHasPropertiesGoal() { - return getResolutionResult().getPom().getPlugins().stream() - .anyMatch(plugin -> isMavenPlugin(plugin, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID) && hasPropertiesGoal(plugin)); - } - @Override public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { if (surefireArgLineWithAgent(getResolutionResult().getPom().getPluginManagement()) != null) { @@ -145,12 +117,23 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { // Nothing to add when the surefire agent, the properties goal, and any `@{argLine}` property // are already present, whether declared here or inherited from a parent's `build/plugins` (#1164). String pluginArgLine = surefireArgLineWithAgent(getResolutionResult().getPom().getPlugins()); - if (pluginArgLine != null && mavenDependencyPluginHasPropertiesGoal() && + boolean mavenDependencyPluginRunsPropertiesGoalAtDefaultPhase = getResolutionResult().getPom().getPlugins().stream() + .anyMatch(plugin -> isMavenPlugin(plugin, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID) && + plugin.getExecutions().stream() + .anyMatch(execution -> execution.getPhase() == null && + execution.getGoals() != null && execution.getGoals().contains(PROPERTIES_GOAL))); + if (pluginArgLine != null && mavenDependencyPluginRunsPropertiesGoalAtDefaultPhase && (!pluginArgLine.contains("@{argLine}") || getResolutionResult().getPom().getProperties().containsKey("argLine"))) { return document; } - maybeAddMavenDependencyPluginWithPropertiesGoal(); + if (!mavenDependencyPluginRunsPropertiesGoalAtDefaultPhase) { + // AddPlugin no-ops when the plugin exists; the visitor below appends only to an already declared plugin. + doAfterVisit(new AddPlugin(MAVEN_PLUGINS_GROUP_ID, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID, null, null, null, + "" + MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG + "", "**/pom.xml").getVisitor()); + doAfterVisit(new AddPropertiesGoalExecutionVisitor()); + } + if (usesRuntimeArgLineProperty()) { doAfterVisit(new AddPropertyVisitor("argLine", "", true)); } @@ -246,23 +229,36 @@ public Xml.Tag visitTag(Xml.Tag tag, AtomicBoolean found) { }.reduce(document, new AtomicBoolean()).get(); } - private static boolean hasPropertiesGoal(Plugin plugin) { - return plugin.getExecutions().stream() - .anyMatch(execution -> execution.getGoals() != null && execution.getGoals().contains("properties")); - } - - @RequiredArgsConstructor - private static class AppendChildTagToParentVisitor extends XmlIsoVisitor { - private final XPathMatcher parentXPathMatcher; - private final Xml.Tag newChildTag; + // Matching the plugin, not each execution, keeps this to one new execution however many the plugin already has. + private static class AddPropertiesGoalExecutionVisitor extends XmlIsoVisitor { + private static final XPathMatcher PLUGIN_MATCHER = new XPathMatcher("/project/build/plugins/plugin"); @Override public Xml.Tag visitTag(Xml.Tag tag, ExecutionContext ctx) { - if (parentXPathMatcher.matches(getCursor()) && - tag.getChildren(newChildTag.getName()).stream().noneMatch(child -> child.getValue().equals(newChildTag.getValue()))) { - return autoFormat(AddToTagVisitor.addToTag(tag, newChildTag, getCursor()), ctx); + Xml.Tag t = super.visitTag(tag, ctx); + // A missing groupId is Maven's own; the matcher excludes pluginManagement. + if (!"plugin".equals(t.getName()) || + !MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID.equals(t.getChildValue("artifactId").orElse(null)) || + !MAVEN_PLUGINS_GROUP_ID.equals(t.getChildValue("groupId").orElse(MAVEN_PLUGINS_GROUP_ID)) || + !PLUGIN_MATCHER.matches(getCursor())) { + return t; } - return super.visitTag(tag, ctx); + + Optional executions = t.getChild("executions"); + if (executions.isPresent() && executions.get().getChildren("execution").stream() + .anyMatch(execution -> !execution.getChild("phase").isPresent() && + execution.getChildren("goals").stream() + .flatMap(goals -> goals.getChildren("goal").stream()) + .anyMatch(goal -> PROPERTIES_GOAL.equals(goal.getValue().orElse(null))))) { + return t; + } + + Xml.Tag scope = executions.orElse(t); + Xml.Tag tagToAdd = executions.isPresent() ? + Xml.Tag.build(MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG) : + Xml.Tag.build("" + MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG + ""); + return (Xml.Tag) new AddToTagVisitor(scope, tagToAdd) + .visitNonNull(t, ctx, getCursor().getParentOrThrow()); } } } diff --git a/src/main/resources/META-INF/rewrite/examples.yml b/src/main/resources/META-INF/rewrite/examples.yml index 6c2d593022..1dfc38ed48 100644 --- a/src/main/resources/META-INF/rewrite/examples.yml +++ b/src/main/resources/META-INF/rewrite/examples.yml @@ -245,6 +245,7 @@ examples: maven-dependency-plugin + get-mockito-agent-path properties diff --git a/src/test/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePluginTest.java b/src/test/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePluginTest.java index cde5b1f83b..5bcd4cfc88 100644 --- a/src/test/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePluginTest.java +++ b/src/test/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePluginTest.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test; import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; import org.openrewrite.test.RecipeSpec; import org.openrewrite.test.RewriteTest; @@ -99,6 +100,7 @@ void addsMockitoAgentArgAndPropertiesGoalToMavenPlugins() { maven-dependency-plugin + get-mockito-agent-path properties @@ -206,6 +208,9 @@ void addsByteBuddyAgentArgWithSurefirePluginAndNoExistingConfigurationWithOlderM + + + get-mockito-agent-path properties @@ -640,6 +645,7 @@ void onlyAddsConfigurationToPomWithMockitoInMultiModuleProject() { maven-dependency-plugin + get-mockito-agent-path properties @@ -720,6 +726,7 @@ void addsMavenSurefireAndDependencyPluginsWhenAbsent() { maven-dependency-plugin + get-mockito-agent-path properties @@ -825,6 +832,9 @@ void addsGoalsTagWithPropertiesGoalToExistingMavenDependencyPluginWhenMissing() + + + get-mockito-agent-path properties @@ -935,6 +945,362 @@ void addsPropertiesGoalToExistingGoalsSectionInMavenDependencyPlugin() { analyize + + + + get-mockito-agent-path + + properties + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -javaagent:${org.mockito:mockito-core:jar} + + + + + + """ + ) + ) + ); + } + + @Issue("https://github.com/moderneinc/customer-requests/issues/2968") + @Test + void addsSeparateExecutionWhenExistingExecutionRunsAfterTests() { + rewriteRun( + mavenProject("test-project", + pomXml( + """ + + 4.0.0 + org.sample + test + ${revision} + + + org.springframework.boot + spring-boot-starter-parent + 3.5.4 + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -javaagent:${org.mockito:mockito-core:jar} + + + + + + """, + """ + + 4.0.0 + org.sample + test + ${revision} + + + org.springframework.boot + spring-boot-starter-parent + 3.5.4 + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + + get-mockito-agent-path + + properties + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -javaagent:${org.mockito:mockito-core:jar} + + + + + + """ + ) + ) + ); + } + + @Test + void addsSeparateExecutionWhenPropertiesGoalIsBoundToAPhase() { + rewriteRun( + mavenProject("test-project", + pomXml( + """ + + 4.0.0 + org.sample + test + ${revision} + + + org.springframework.boot + spring-boot-starter-parent + 3.5.4 + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + properties + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -javaagent:${org.mockito:mockito-core:jar} + + + + + + """, + """ + + 4.0.0 + org.sample + test + ${revision} + + + org.springframework.boot + spring-boot-starter-parent + 3.5.4 + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + properties + + + + get-mockito-agent-path + + properties + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -javaagent:${org.mockito:mockito-core:jar} + + + + + + """ + ) + ) + ); + } + + @Test + void addsSingleExecutionWhenMavenDependencyPluginHasSeveralExecutions() { + rewriteRun( + mavenProject("test-project", + pomXml( + """ + + 4.0.0 + org.sample + test + ${revision} + + + org.springframework.boot + spring-boot-starter-parent + 3.5.4 + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + + unpack + prepare-package + + unpack-dependencies + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -javaagent:${org.mockito:mockito-core:jar} + + + + + + """, + """ + + 4.0.0 + org.sample + test + ${revision} + + + org.springframework.boot + spring-boot-starter-parent + 3.5.4 + + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + maven-dependency-plugin + + + copy-dependencies + package + + copy-dependencies + + + + unpack + prepare-package + + unpack-dependencies + + + + get-mockito-agent-path + properties @@ -1238,6 +1604,7 @@ void updatesIndividualPomsWhenParentPomManagesSurefirePluginWithoutAgentConfigur maven-dependency-plugin + get-mockito-agent-path properties @@ -1328,6 +1695,7 @@ void updatesIndividualPomsWhenParentPomManagesSurefirePluginWithoutAgentConfigur maven-dependency-plugin + get-mockito-agent-path properties @@ -1684,6 +2052,7 @@ void addsSurefireAgentToModuleWhenParentReactorPomManagesPluginsWithoutDeclaring maven-dependency-plugin + get-mockito-agent-path properties @@ -1752,6 +2121,7 @@ void omitsLateArgLineReplacementWhenNothingSetsArgLineAtRuntime() { maven-dependency-plugin + get-mockito-agent-path properties @@ -1836,6 +2206,7 @@ void usesLateArgLineReplacementWhenJacocoPluginPresent() { maven-dependency-plugin + get-mockito-agent-path properties @@ -1934,6 +2305,7 @@ void usesLateArgLineReplacementWhenJacocoPluginOnlyActiveInProfile() { maven-dependency-plugin + get-mockito-agent-path properties @@ -2007,6 +2379,7 @@ void usesLateArgLineReplacementWhenArgLinePropertyAlreadyDeclared() { maven-dependency-plugin + get-mockito-agent-path properties From 95274e1acea321909518ce57bcf5b171d54b095c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20B=C3=B6gershausen?= Date: Fri, 7 Aug 2026 15:37:35 +0200 Subject: [PATCH 2/2] Apply the plugin edits during the visit instead of scheduling them 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. --- ...MockitoJavaAgentToMavenSurefirePlugin.java | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java b/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java index de712041ba..3411e16569 100644 --- a/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java +++ b/src/main/java/org/openrewrite/java/migrate/AddMockitoJavaAgentToMavenSurefirePlugin.java @@ -29,7 +29,6 @@ import org.openrewrite.maven.MavenIsoVisitor; import org.openrewrite.maven.search.DependencyInsight; import org.openrewrite.maven.search.FindPlugin; -import org.openrewrite.maven.trait.MavenPlugin; import org.openrewrite.maven.tree.Plugin; import org.openrewrite.maven.tree.ResolvedDependency; import org.openrewrite.maven.tree.Scope; @@ -122,29 +121,27 @@ public Xml.Document visitDocument(Xml.Document document, ExecutionContext ctx) { plugin.getExecutions().stream() .anyMatch(execution -> execution.getPhase() == null && execution.getGoals() != null && execution.getGoals().contains(PROPERTIES_GOAL))); - if (pluginArgLine != null && mavenDependencyPluginRunsPropertiesGoalAtDefaultPhase && + if (mavenDependencyPluginRunsPropertiesGoalAtDefaultPhase && pluginArgLine != null && (!pluginArgLine.contains("@{argLine}") || getResolutionResult().getPom().getProperties().containsKey("argLine"))) { return document; } + Xml.Document d = document; if (!mavenDependencyPluginRunsPropertiesGoalAtDefaultPhase) { // AddPlugin no-ops when the plugin exists; the visitor below appends only to an already declared plugin. - doAfterVisit(new AddPlugin(MAVEN_PLUGINS_GROUP_ID, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID, null, null, null, - "" + MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG + "", "**/pom.xml").getVisitor()); - doAfterVisit(new AddPropertiesGoalExecutionVisitor()); + d = (Xml.Document) new AddPlugin(MAVEN_PLUGINS_GROUP_ID, MAVEN_DEPENDENCY_PLUGIN_ARTIFACT_ID, null, null, null, + "" + MAVEN_DEPENDENCY_PLUGIN_EXECUTION_TAG + "", "**/pom.xml") + .getVisitor().visitNonNull(d, ctx); + d = (Xml.Document) new AddPropertiesGoalExecutionVisitor().visitNonNull(d, ctx); } - if (usesRuntimeArgLineProperty()) { - doAfterVisit(new AddPropertyVisitor("argLine", "", true)); - } - - if (FindPlugin.find(document, "org.apache.maven.plugins", "maven-surefire-plugin").isEmpty()) { - doAfterVisit(new AddPlugin("org.apache.maven.plugins", "maven-surefire-plugin", null, - String.format(CONFIGURATION_TAG_TEMPLATE, newArgLineValue(getArgLineJavaAgentArgument())), null, - null, "**/pom.xml").getVisitor()); - return document; + d = (Xml.Document) new AddPropertyVisitor("argLine", "", true).visitNonNull(d, ctx); } - return super.visitDocument(document, ctx); + return FindPlugin.find(d, MAVEN_PLUGINS_GROUP_ID, MAVEN_SUREFIRE_PLUGIN_ARTIFACT_ID).isEmpty() ? + (Xml.Document) new AddPlugin(MAVEN_PLUGINS_GROUP_ID, MAVEN_SUREFIRE_PLUGIN_ARTIFACT_ID, null, + String.format(CONFIGURATION_TAG_TEMPLATE, newArgLineValue(getArgLineJavaAgentArgument())), null, + null, "**/pom.xml").getVisitor().visitNonNull(d, ctx) : + super.visitDocument(d, ctx); } @Override