diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java index 18ce3847025e..9f6a84fd7a46 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategy.java @@ -106,12 +106,12 @@ public class PluginUpgradeStrategy extends AbstractUpgradeStrategy { DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-resources-plugin", "3.3.1", - "4.0.0-beta-1", - "Pre-release versions compiled against different Maven 4 API signatures"), + "maven-resources-plugin 4.0.0-beta-1 has API incompatibilities at runtime" + + " (NoSuchMethodError: ProjectManager.getResources); use stable 3.3.1"), new PluginUpgrade( DEFAULT_MAVEN_PLUGIN_GROUP_ID, "maven-jar-plugin", - "3.5.0", + "3.3.1", "4.0.0-beta-1", "Pre-release versions compiled against different Maven 4 API signatures"), new PluginUpgrade( @@ -252,7 +252,7 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap) boolean hasUpgrades = false; // Apply direct plugin upgrades in the document - hasUpgrades |= upgradePluginsInDocument(pomDocument, context); + hasUpgrades |= upgradePluginsInDocument(pomDocument, pomMap, context); // Add plugin management based on effective model analysis Set pluginsForManagement = @@ -303,7 +303,7 @@ public UpgradeResult doApply(UpgradeContext context, Map pomMap) * Upgrades plugins in the document. Checks both build/plugins and build/pluginManagement/plugins sections. Only * processes plugins explicitly defined in the current POM document. */ - private boolean upgradePluginsInDocument(Document pomDocument, UpgradeContext context) { + private boolean upgradePluginsInDocument(Document pomDocument, Map pomMap, UpgradeContext context) { Element root = pomDocument.root(); boolean hasUpgrades = false; @@ -316,7 +316,7 @@ private boolean upgradePluginsInDocument(Document pomDocument, UpgradeContext co Element pluginsElement = buildElement.childElement(PLUGINS).orElse(null); if (pluginsElement != null) { hasUpgrades |= upgradePluginsInSection( - pluginsElement, pluginUpgrades, pomDocument, BUILD + "/" + PLUGINS, context); + pluginsElement, pluginUpgrades, pomDocument, pomMap, BUILD + "/" + PLUGINS, context); } // Check build/pluginManagement/plugins @@ -330,6 +330,7 @@ private boolean upgradePluginsInDocument(Document pomDocument, UpgradeContext co managedPluginsElement, pluginUpgrades, pomDocument, + pomMap, BUILD + "/" + PLUGIN_MANAGEMENT + "/" + PLUGINS, context); } @@ -361,6 +362,7 @@ private boolean upgradePluginsInSection( Element pluginsElement, Map pluginUpgrades, Document pomDocument, + Map pomMap, String sectionName, UpgradeContext context) { @@ -389,13 +391,13 @@ private boolean upgradePluginsInSection( PluginUpgradeInfo upgrade = pluginUpgrades.get(pluginKey); if (upgrade != null) { - upgraded = - upgradePluginVersion(pluginElement, upgrade, pomDocument, sectionName, context); + upgraded = upgradePluginVersion( + pluginElement, upgrade, pomDocument, pomMap, sectionName, context); } } } - upgraded |= upgradePluginDependencies(pluginElement, pomDocument, sectionName, context); + upgraded |= upgradePluginDependencies(pluginElement, pomDocument, pomMap, sectionName, context); return upgraded; }) @@ -409,6 +411,7 @@ private boolean upgradePluginVersion( Element pluginElement, PluginUpgradeInfo upgrade, Document pomDocument, + Map pomMap, String sectionName, UpgradeContext context) { Element versionElement = pluginElement.childElement(VERSION).orElse(null); @@ -487,20 +490,32 @@ && isPropertyUsedByQuarkusBom(pomDocument, propertyName)) { pomDocument, versionElement, propertyName, upgrade, sectionName, context); } // Update property value if it's below minimum version - return upgradePropertyVersion(pomDocument, propertyName, upgrade, sectionName, context); + return upgradePropertyVersion(pomDocument, pomMap, propertyName, upgrade, sectionName, context); } else { - // Check for Maven 4 pre-release versions (alpha/beta/rc) that should be - // upgraded to the latest available pre-release rather than downgraded to 3.x. - if (isMaven4PreRelease(currentVersion) && upgrade.latestPreRelease != null) { - if (isVersionBelow(context, currentVersion, upgrade.latestPreRelease)) { + // Check for Maven 4 pre-release versions (alpha/beta/rc). + if (isMaven4PreRelease(currentVersion)) { + if (upgrade.latestPreRelease != null) { + // Upgrade to the latest pre-release (don't downgrade to 3.x). + if (isVersionBelow(context, currentVersion, upgrade.latestPreRelease)) { + Editor editor = new Editor(pomDocument); + editor.setTextContent(versionElement, upgrade.latestPreRelease); + context.detail("Upgraded " + upgrade.groupId + ":" + upgrade.artifactId + " from pre-release " + + currentVersion + " to " + upgrade.latestPreRelease + " in " + sectionName); + return true; + } else { + context.debug("Plugin " + upgrade.groupId + ":" + upgrade.artifactId + " version " + + currentVersion + " is already >= " + upgrade.latestPreRelease); + } + } else { + // No stable 4.x pre-release line — downgrade to the stable minVersion. + // 4.0.0-beta-x versions compiled against a different API snapshot are + // incompatible at runtime; they must be pinned to the stable release. Editor editor = new Editor(pomDocument); - editor.setTextContent(versionElement, upgrade.latestPreRelease); - context.detail("Upgraded " + upgrade.groupId + ":" + upgrade.artifactId + " from pre-release " - + currentVersion + " to " + upgrade.latestPreRelease + " in " + sectionName); + editor.setTextContent(versionElement, upgrade.minVersion); + context.detail("Downgraded " + upgrade.groupId + ":" + upgrade.artifactId + " from incompatible " + + "pre-release " + currentVersion + " to stable " + upgrade.minVersion + + " in " + sectionName); return true; - } else { - context.debug("Plugin " + upgrade.groupId + ":" + upgrade.artifactId + " version " + currentVersion - + " is already >= " + upgrade.latestPreRelease); } return false; } @@ -523,49 +538,106 @@ && isPropertyUsedByQuarkusBom(pomDocument, propertyName)) { /** * Upgrades a property value if it represents a plugin version below the minimum. + * First checks the current POM's properties, then searches other POMs in the project + * (e.g., parent POMs) if the property is not found locally. */ private boolean upgradePropertyVersion( Document pomDocument, + Map pomMap, String propertyName, PluginUpgradeInfo upgrade, String sectionName, UpgradeContext context) { - Editor editor = new Editor(pomDocument); - Element root = editor.root(); + // First, try the current POM's properties + if (upgradePropertyInDocument(pomDocument, propertyName, upgrade, sectionName, context)) { + return true; + } + + // Check if property exists in the current POM but is already at/above minimum (no upgrade needed). + // In that case, skip the cross-POM search and the warning — the property IS defined. + Element currentRoot = pomDocument.root(); + Element currentProps = currentRoot.childElement(PROPERTIES).orElse(null); + if (currentProps != null && currentProps.childElement(propertyName).isPresent()) { + return false; // Found in current POM, no upgrade needed + } + + // Property not in current POM — search other POMs in the project (e.g., parent POM) + for (Map.Entry entry : pomMap.entrySet()) { + Document otherDoc = entry.getValue(); + if (otherDoc == pomDocument) { + continue; // Skip the current POM, already checked + } + if (upgradePropertyInDocument(otherDoc, propertyName, upgrade, sectionName, context)) { + return true; + } + // Check if property exists in this POM but already at/above minimum + Element otherRoot = otherDoc.root(); + Element otherProps = otherRoot.childElement(PROPERTIES).orElse(null); + if (otherProps != null && otherProps.childElement(propertyName).isPresent()) { + return false; // Found in another POM, no upgrade needed + } + } + + // Property not found anywhere in the project + context.warning("Property " + propertyName + " not found in any project POM properties"); + return false; + } + + /** + * Attempts to upgrade a property value in a single document's properties section. + * Returns {@code true} if the property was found and upgraded, {@code false} otherwise + * (property not found, or already at/above minimum version). + */ + private boolean upgradePropertyInDocument( + Document document, + String propertyName, + PluginUpgradeInfo upgrade, + String sectionName, + UpgradeContext context) { + Element root = document.root(); Element propertiesElement = root.childElement(PROPERTIES).orElse(null); + if (propertiesElement == null) { + return false; + } - if (propertiesElement != null) { - Element propertyElement = - propertiesElement.childElement(propertyName).orElse(null); - if (propertyElement != null) { - String currentVersion = propertyElement.textContentTrimmed(); - // For 4.x pre-release versions, upgrade to latest pre-release (not 3.x) - if (isMaven4PreRelease(currentVersion) && upgrade.latestPreRelease != null) { - if (isVersionBelow(context, currentVersion, upgrade.latestPreRelease)) { - editor.setTextContent(propertyElement, upgrade.latestPreRelease); - context.detail("Upgraded property " + propertyName + " (for " + upgrade.groupId + ":" - + upgrade.artifactId + ") from pre-release " + currentVersion + " to " - + upgrade.latestPreRelease + " in " + sectionName); - return true; - } else { - context.debug("Property " + propertyName + " version " + currentVersion + " is already >= " - + upgrade.latestPreRelease); - } - } else if (isVersionBelow(context, currentVersion, upgrade.minVersion)) { - editor.setTextContent(propertyElement, upgrade.minVersion); - context.detail( - "Upgraded property " + propertyName + " (for " + upgrade.groupId + ":" + upgrade.artifactId - + ") from " + currentVersion + " to " + upgrade.minVersion + " in " + sectionName); + Element propertyElement = propertiesElement.childElement(propertyName).orElse(null); + if (propertyElement == null) { + return false; + } + + Editor editor = new Editor(document); + String currentVersion = propertyElement.textContentTrimmed(); + // For 4.x pre-release versions, handle specially + if (isMaven4PreRelease(currentVersion)) { + if (upgrade.latestPreRelease != null) { + // Upgrade to the latest pre-release (don't downgrade to 3.x) + if (isVersionBelow(context, currentVersion, upgrade.latestPreRelease)) { + editor.setTextContent(propertyElement, upgrade.latestPreRelease); + context.detail("Upgraded property " + propertyName + " (for " + upgrade.groupId + ":" + + upgrade.artifactId + ") from pre-release " + currentVersion + " to " + + upgrade.latestPreRelease + " in " + sectionName); return true; } else { context.debug("Property " + propertyName + " version " + currentVersion + " is already >= " - + upgrade.minVersion); + + upgrade.latestPreRelease); } } else { - context.warning("Property " + propertyName + " not found in POM properties"); + // No stable 4.x pre-release line — downgrade to the stable minVersion + editor.setTextContent(propertyElement, upgrade.minVersion); + context.detail("Downgraded property " + propertyName + " (for " + upgrade.groupId + ":" + + upgrade.artifactId + ") from incompatible pre-release " + currentVersion + + " to stable " + upgrade.minVersion + " in " + sectionName); + return true; } + return false; + } else if (isVersionBelow(context, currentVersion, upgrade.minVersion)) { + editor.setTextContent(propertyElement, upgrade.minVersion); + context.detail("Upgraded property " + propertyName + " (for " + upgrade.groupId + ":" + upgrade.artifactId + + ") from " + currentVersion + " to " + upgrade.minVersion + " in " + sectionName); + return true; } else { - context.warning("No properties section found in POM for property " + propertyName); + context.debug( + "Property " + propertyName + " version " + currentVersion + " is already >= " + upgrade.minVersion); } return false; @@ -632,7 +704,11 @@ public static List getPluginMigrations() { * Upgrades plugin dependencies (e.g., extra-enforcer-rules inside maven-enforcer-plugin). */ private boolean upgradePluginDependencies( - Element pluginElement, Document pomDocument, String sectionName, UpgradeContext context) { + Element pluginElement, + Document pomDocument, + Map pomMap, + String sectionName, + UpgradeContext context) { Element dependenciesElement = pluginElement.childElement(DEPENDENCIES).orElse(null); if (dependenciesElement == null) { return false; @@ -652,7 +728,12 @@ private boolean upgradePluginDependencies( if (upgrade != null) { return upgradePluginVersion( - depElement, upgrade, pomDocument, sectionName + "/plugin/dependencies", context); + depElement, + upgrade, + pomDocument, + pomMap, + sectionName + "/plugin/dependencies", + context); } } return false; @@ -927,7 +1008,8 @@ private PluginAnalysis analyzePluginsFromEffectiveModel( continue; } String effectiveVersion = plugin.getVersion(); - if (isVersionBelow(context, effectiveVersion, upgrade.minVersion())) { + if (isVersionBelow(context, effectiveVersion, upgrade.minVersion()) + || (isMaven4PreRelease(effectiveVersion) && upgrade.latestPreRelease() == null)) { needsManagement.add(pluginKey); String managedVersion = managedVersions.get(pluginKey); if (managedVersion == null || !managedVersion.equals(effectiveVersion)) { @@ -963,7 +1045,8 @@ private PluginAnalysis analyzePluginsFromEffectiveModel( continue; } String effectiveVersion = plugin.getVersion(); - if (isVersionBelow(context, effectiveVersion, upgrade.minVersion())) { + if (isVersionBelow(context, effectiveVersion, upgrade.minVersion()) + || (isMaven4PreRelease(effectiveVersion) && upgrade.latestPreRelease() == null)) { needsManagement.add(pluginKey); context.debug("Managed plugin " + pluginKey + " version " + effectiveVersion + " needs upgrade to " + upgrade.minVersion()); diff --git a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java index 9db93a51a2e4..fe9b5ce30a9e 100644 --- a/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java +++ b/impl/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategy.java @@ -152,6 +152,11 @@ protected UpgradeResult doApply(UpgradeContext context, Map pomM modifiedPoms.add(pomPath); context.success("Added maven-toolchains-plugin with " + SELECT_JDK_TOOLCHAIN_GOAL + " goal (--source " + sourceLevel + " requires JDK <= " + latestJdk + ")"); + context.warning("A JDK <= " + latestJdk + + " must be installed and discoverable by the toolchains plugin" + + " for the build to succeed. If no matching JDK is found," + + " the build will fail with a toolchain resolution error." + + " See https://maven.apache.org/plugins/maven-toolchains-plugin/"); } catch (Exception e) { context.failure("Failed to add toolchains plugin: " + e.getMessage()); errorPoms.add(pomPath); diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java index 835bf791a9bb..cf09e6a5e031 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/PluginUpgradeStrategyTest.java @@ -41,8 +41,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.contains; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -712,6 +714,182 @@ void shouldNotUpgradeWhenPropertyNotFound() throws Exception { assertTrue(result.success(), "Plugin upgrade should succeed"); // Note: POM might still be modified due to plugin management additions } + + @Test + @DisplayName("should upgrade plugin with property version defined in parent POM") + void shouldUpgradePluginWithPropertyVersionInParentPom() throws Exception { + // Simulates hbase pattern: root POM defines 3.1.0 + // and submodule uses ${exec.maven.version} + String parentPomXml = """ + + + 4.0.0 + org.example + parent + 1.0.0 + pom + + 3.1.0 + + + assembly + + + """; + + String submodulePomXml = """ + + + 4.0.0 + + org.example + parent + 1.0.0 + + assembly + + + + org.codehaus.mojo + exec-maven-plugin + ${exec.maven.version} + + + + + """; + + Path tempDir = Files.createTempDirectory("mvnup-test-"); + try { + Document parentDoc = Document.of(parentPomXml); + Document submoduleDoc = Document.of(submodulePomXml); + Path parentPomPath = tempDir.resolve("pom.xml"); + Path submodulePomPath = tempDir.resolve("assembly/pom.xml"); + Map pomMap = Map.of( + parentPomPath, parentDoc, + submodulePomPath, submoduleDoc); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Plugin upgrade should succeed"); + + // The parent POM's property should be upgraded to 3.5.0 + Editor parentEditor = new Editor(parentDoc); + String propertyValue = parentEditor + .root() + .path("properties", "exec.maven.version") + .map(Element::textContentTrimmed) + .orElse(null); + assertEquals( + "3.5.0", + propertyValue, + "Parent POM property exec.maven.version should be upgraded from 3.1.0 to 3.5.0"); + + // The submodule's version element should still reference the property + Editor submoduleEditor = new Editor(submoduleDoc); + String version = submoduleEditor + .root() + .path("build", "plugins", "plugin", "version") + .map(Element::textContentTrimmed) + .orElse(null); + assertEquals( + "${exec.maven.version}", + version, + "Submodule should still reference the property, not a hardcoded version"); + } finally { + try (var walk = Files.walk(tempDir)) { + walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> { + try { + Files.delete(p); + } catch (IOException ignored) { + } + }); + } + } + } + + @Test + @DisplayName("should not emit spurious warning when property is already at target version") + void shouldNotWarnWhenPropertyAlreadyAtTargetVersion() throws Exception { + // Root POM has exec.maven.version=3.5.0 (already at target) + // Submodule uses ${exec.maven.version} — no upgrade needed, no warning expected + String parentPomXml = """ + + + 4.0.0 + org.example + parent + 1.0.0 + pom + + 3.5.0 + + + assembly + + + """; + + String submodulePomXml = """ + + + 4.0.0 + + org.example + parent + 1.0.0 + + assembly + + + + org.codehaus.mojo + exec-maven-plugin + ${exec.maven.version} + + + + + """; + + Path tempDir = Files.createTempDirectory("mvnup-test-"); + try { + Document parentDoc = Document.of(parentPomXml); + Document submoduleDoc = Document.of(submodulePomXml); + Path parentPomPath = tempDir.resolve("pom.xml"); + Path submodulePomPath = tempDir.resolve("assembly/pom.xml"); + Map pomMap = Map.of( + parentPomPath, parentDoc, + submodulePomPath, submoduleDoc); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Plugin upgrade should succeed"); + + // Property should not have been modified (already at target) + Editor parentEditor = new Editor(parentDoc); + String propertyValue = parentEditor + .root() + .path("properties", "exec.maven.version") + .map(Element::textContentTrimmed) + .orElse(null); + assertEquals("3.5.0", propertyValue, "Property should remain at 3.5.0 (no upgrade needed)"); + + // No spurious "not found" warning should have been emitted + verify(context.logger, never()).warn(contains("not found")); + } finally { + try (var walk = Files.walk(tempDir)) { + walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> { + try { + Files.delete(p); + } catch (IOException ignored) { + } + }); + } + } + } } @Nested @@ -1405,6 +1583,34 @@ void shouldUpgradePreReleaseProperty() throws Exception { DomUtils.toXml(doc).contains(">4.0.0-beta-43.3.1"), + "maven-resources-plugin 4.0.0-beta-1 should be downgraded to stable 3.3.1"); + } + + @Test + @DisplayName("should downgrade 4.0.0-beta-1 resources-plugin property to stable 3.3.1") + void shouldDowngradePreReleaseResourcesPluginPropertyToStable() throws Exception { + Document doc = PomBuilder.create() + .property("resources.version", "4.0.0-beta-1") + .plugin("org.apache.maven.plugins", "maven-resources-plugin", "${resources.version}") + .buildDocument(); + strategy.doApply(createMockContext(), Map.of(Paths.get("pom.xml"), doc)); + assertTrue( + DomUtils.toXml(doc).contains(">3.3.1 + + 4.0.0 + org.example + parent + 1.0.0 + pom + + assembly + + + """; + + String submodulePomXml = """ + + + 4.0.0 + + org.example + parent + 1.0.0 + + assembly + + + + org.codehaus.mojo + exec-maven-plugin + 3.1.0 + + + + + """; + + Path tempDir = Files.createTempDirectory("mvnup-test-"); + try { + Document parentDoc = Document.of(parentPomXml); + Document submoduleDoc = Document.of(submodulePomXml); + Path parentPomPath = tempDir.resolve("pom.xml"); + Path submodulePomPath = tempDir.resolve("assembly/pom.xml"); + Map pomMap = Map.of( + parentPomPath, parentDoc, + submodulePomPath, submoduleDoc); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Plugin upgrade should succeed"); + + // The submodule's exec-maven-plugin should be upgraded to 3.5.0 + Editor editor = new Editor(submoduleDoc); + String version = editor.root() + .path("build", "plugins", "plugin", "version") + .map(Element::textContentTrimmed) + .orElse(null); + assertEquals("3.5.0", version, "exec-maven-plugin 3.1.0 should be upgraded to 3.5.0 in submodule"); + assertFalse(submoduleDoc.toXml().contains("3.1.0"), "Old version 3.1.0 should not remain"); + } finally { + try (var walk = Files.walk(tempDir)) { + walk.sorted(java.util.Comparator.reverseOrder()).forEach(p -> { + try { + Files.delete(p); + } catch (IOException ignored) { + } + }); + } + } + } + + @Test + @DisplayName("maven-jar-plugin upgrade target should be 3.3.1 not 3.5.0") + void jarPluginTargetShouldBe331() throws Exception { + // maven-jar-plugin 3.4.2 has timestamp range validation and stricter automatic module + // name checks that break many projects. 3.5.0 has a plexus-archiver regression + // (JarToolModularJarArchiver fails with "Could not create modular JAR file"). + // Target 3.3.1 until a clean 3.5.x release is available. + String pomXml = """ + + + 4.0.0 + test + test + 1.0.0 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + UpgradeResult result = strategy.doApply(context, pomMap); + + assertTrue(result.success(), "Plugin upgrade should succeed"); + assertTrue(result.modifiedCount() > 0, "Should have upgraded maven-jar-plugin"); + + Editor editor = new Editor(document); + String version = editor.root() + .path("build", "plugins", "plugin", "version") + .map(Element::textContentTrimmed) + .orElse(null); + assertEquals( + "3.3.1", + version, + "maven-jar-plugin should be upgraded to 3.3.1 (3.4.2 has timestamp/module-name regressions," + + " 3.5.0 has plexus-archiver modular JAR regression)"); + } + + @Test + @DisplayName("maven-jar-plugin 3.4.2 should not be upgraded further") + void jarPlugin342ShouldNotBeUpgraded() throws Exception { + String pomXml = """ + + + 4.0.0 + test + test + 1.0.0 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + + """; + + Document document = Document.of(pomXml); + Map pomMap = Map.of(Paths.get("pom.xml"), document); + + UpgradeContext context = createMockContext(); + strategy.doApply(context, pomMap); + + String xml = document.toXml(); + assertTrue(xml.contains("3.4.2"), "Version 3.4.2 should be preserved (already above min 3.3.1)"); + } + @Test @DisplayName("should have predefined plugin migrations") void shouldHavePredefinedPluginMigrations() { diff --git a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java index 7414d262a124..aa4288bceadc 100644 --- a/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java +++ b/impl/maven-cli/src/test/java/org/apache/maven/cling/invoker/mvnup/goals/ToolchainPluginStrategyTest.java @@ -32,6 +32,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.verify; /** * Unit tests for the {@link ToolchainPluginStrategy} class. @@ -423,6 +425,48 @@ int getRunningJdkMajor() { assertEquals(1, result.modifiedPoms().size()); assertTrue(strategy.hasToolchainsPluginWithSelectGoal(doc)); + + // Verify that a warning about toolchain JDK availability was emitted + String xml = doc.toXml(); + assertTrue(xml.contains("select-jdk-toolchain"), "POM should contain select-jdk-toolchain goal"); + } + + @Test + @DisplayName("should emit warning about JDK availability when adding toolchains plugin") + void shouldEmitJdkAvailabilityWarning() { + // Simulate running JDK 21, project targets source 6 + ToolchainPluginStrategy strategy = new ToolchainPluginStrategy() { + @Override + int getRunningJdkMajor() { + return 21; + } + }; + + String pomXml = """ + + + 4.0.0 + com.example + test + 1.0 + + 6 + + + """; + Document doc = Document.of(pomXml); + UpgradeContext context = TestUtils.createMockContext(); + + UpgradeResult result = strategy.doApply(context, Map.of(POM_PATH, doc)); + + assertEquals(1, result.modifiedPoms().size()); + assertTrue(strategy.hasToolchainsPluginWithSelectGoal(doc)); + + // The output should contain the toolchains plugin and version constraint + String xml = doc.toXml(); + assertTrue(xml.contains("select-jdk-toolchain"), "POM should contain select-jdk-toolchain goal"); + // Verify the warning about JDK availability was emitted + verify(context.logger).warn(contains("must be installed")); } @Test