diff --git a/src/main/java/org/codehaus/mojo/versions/AbstractVersionsReport.java b/src/main/java/org/codehaus/mojo/versions/AbstractVersionsReport.java index 8f9f32cc38..baf91b60a8 100644 --- a/src/main/java/org/codehaus/mojo/versions/AbstractVersionsReport.java +++ b/src/main/java/org/codehaus/mojo/versions/AbstractVersionsReport.java @@ -57,30 +57,13 @@ public abstract class AbstractVersionsReport extends AbstractMavenReport { - - /** - * Doxia Site Renderer component. - * - * @since 1.0-alpha-3 - */ - @Component - private Renderer siteRenderer; - /** * Internationalization component. * * @since 1.0-alpha-3 */ @Component - private I18N i18n; - - /** - * The Maven Project. - * - * @since 1.0-alpha-3 - */ - @Parameter( defaultValue = "${project}", required = true, readonly = true ) - private MavenProject project; + protected I18N i18n; @Component protected RepositorySystem repositorySystem; @@ -91,16 +74,6 @@ public abstract class AbstractVersionsReport @Component private ArtifactResolver resolver; - /** - * The output directory for the report. Note that this parameter is only evaluated if the goal is run directly from - * the command line. If the goal is run indirectly as part of a site generation, the output directory configured in - * the Maven Site Plugin is used instead. - * - * @since 1.0-alpha-3 - */ - @Parameter( defaultValue = "${project.reporting.outputDirectory}", required = true ) - private File outputDirectory; - /** * Skip entire check. * diff --git a/src/main/java/org/codehaus/mojo/versions/DependencyUpdatesReport.java b/src/main/java/org/codehaus/mojo/versions/DependencyUpdatesReport.java index 6a5c14c7af..a9b251a88f 100644 --- a/src/main/java/org/codehaus/mojo/versions/DependencyUpdatesReport.java +++ b/src/main/java/org/codehaus/mojo/versions/DependencyUpdatesReport.java @@ -20,13 +20,12 @@ */ import java.io.File; -import java.util.Collections; -import java.util.Iterator; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.apache.commons.lang3.StringUtils; import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; import org.apache.maven.doxia.sink.Sink; import org.apache.maven.model.Dependency; @@ -36,7 +35,9 @@ import org.apache.maven.reporting.MavenReportException; import org.codehaus.mojo.versions.api.ArtifactVersions; import org.codehaus.mojo.versions.utils.DependencyComparator; -import org.codehaus.plexus.util.StringUtils; + +import static java.util.Collections.EMPTY_MAP; +import static org.codehaus.mojo.versions.utils.MiscUtils.filter; /** * Generates a report of available updates for the dependencies of a project. @@ -44,38 +45,54 @@ * @author Stephen Connolly * @since 1.0-beta-1 */ -@Mojo( name = "dependency-updates-report", requiresProject = true, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true) -public class DependencyUpdatesReport - extends AbstractVersionsReport +@Mojo( name = "dependency-updates-report", + requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true ) +public class DependencyUpdatesReport extends AbstractVersionsReport { /** * Whether to process the dependencyManagement in pom or not. - * + * * @since 2.5 */ @Parameter( property = "processDependencyManagement", defaultValue = "true" ) - private boolean processDependencyManagement; + protected boolean processDependencyManagement; /** * Whether to process the depdendencyManagement part transitive or not. * In case of <type>pom</type>and * <scope>import</scope> this means - * by default to report also the imported dependencies. + * by default to report also the imported dependencies. * If processTransitive is set to false the report will only show * updates of the imported pom it self. - * + * * @since 2.5 Note: Currently in experimental state. */ @Parameter( property = "processDependencyManagementTransitive", defaultValue = "true" ) - private boolean processDependencyManagementTransitive; + protected boolean processDependencyManagementTransitive; /** * Report formats (html and/or xml). HTML by default. - * */ @Parameter( property = "dependencyUpdatesReportFormats", defaultValue = "html" ) - private String[] formats = new String[] { "html" }; + protected String[] formats = new String[] {"html"}; + + /** + * If true, only shows the subsection of the dependencyManagement artifacts that + * are actually used in the project's dependency graph. false by default. + * + * @since 2.12 + */ + @Parameter( property = "onlyProjectDependencies", defaultValue = "false" ) + protected boolean onlyProjectDependencies; + + /** + * If true, only shows upgradable dependencies in the report. false by default. + * + * @since 2.12 + */ + @Parameter( property = "onlyUpgradable", defaultValue = "false" ) + protected boolean onlyUpgradable; /** * {@inheritDoc} @@ -97,82 +114,100 @@ public boolean canGenerateReport() * generates an empty report in case there are no sources to generate a report with * * @param locale the locale to generate the report for. - * @param sink the report formatting tool + * @param sink the report formatting tool */ - protected void doGenerateReport( Locale locale, Sink sink ) - throws MavenReportException + protected void doGenerateReport( Locale locale, Sink sink ) throws MavenReportException { Set dependencies = new TreeSet<>( new DependencyComparator() ); dependencies.addAll( getProject().getDependencies() ); Set dependencyManagement = new TreeSet<>( new DependencyComparator() ); - if ( processDependencyManagementTransitive ) + if ( processDependencyManagement ) { - if ( getProject().getDependencyManagement() != null - && getProject().getDependencyManagement().getDependencies() != null ) + if ( processDependencyManagementTransitive ) { - for ( Dependency dep : getProject().getDependencyManagement().getDependencies() ) + if ( getProject().getDependencyManagement() != null + && getProject().getDependencyManagement().getDependencies() != null ) { - getLog().debug( "Dpmg: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getVersion() - + ":" + dep.getType() + ":" + dep.getScope() ); + for ( Dependency dep : getProject().getDependencyManagement().getDependencies() ) + { + getLog().debug( + "Dpmg: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" + dep.getVersion() + ":" + + dep.getType() + ":" + dep.getScope() ); + } + dependencyManagement.addAll( getProject().getDependencyManagement().getDependencies() ); } - dependencyManagement.addAll( getProject().getDependencyManagement().getDependencies() ); } - } - else - { - if ( getProject().getOriginalModel().getDependencyManagement() != null - && getProject().getOriginalModel().getDependencyManagement().getDependencies() != null ) + else { - // Using the original model to get the original dependencyManagement entries and - // not the interpolated model. - // TODO: I'm not 100% sure if this will work correctly in all cases. - for ( Dependency dep : getProject().getOriginalModel().getDependencyManagement().getDependencies() ) + if ( getProject().getOriginalModel().getDependencyManagement() != null + && getProject().getOriginalModel().getDependencyManagement().getDependencies() != null ) { - getLog().debug( "Original Dpmg: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" - + dep.getVersion() + ":" + dep.getType() + ":" + dep.getScope() ); + // Using the original model to get the original dependencyManagement entries and + // not the interpolated model. + // TODO: I'm not 100% sure if this will work correctly in all cases. + for ( Dependency dep : getProject().getOriginalModel().getDependencyManagement().getDependencies() ) + { + getLog().debug( "Original Dpmg: " + dep.getGroupId() + ":" + dep.getArtifactId() + ":" + + dep.getVersion() + ":" + dep.getType() + ":" + dep.getScope() ); + } + dependencyManagement.addAll( + getProject().getOriginalModel().getDependencyManagement().getDependencies() ); } - dependencyManagement.addAll( getProject().getOriginalModel().getDependencyManagement().getDependencies() ); } - } - if ( processDependencyManagement ) - { - dependencies = removeDependencyManagment( dependencies, dependencyManagement ); + if ( !onlyProjectDependencies ) + { + // Retains only dependencies not present in dependencyManagement + dependencies.removeIf( dep -> dependencyManagement.stream().anyMatch( dmDep -> match( dep, dmDep ) ) ); + } + else + { + // Retain only dependencies in dependencyManagement that are also present in dependencies + dependencyManagement.removeIf( dep -> dependencies.stream().noneMatch( dmDep -> match( dep, dmDep ) ) ); + } } try { Map dependencyUpdates = - getHelper().lookupDependenciesUpdates( dependencies, false ); + getHelper().lookupDependenciesUpdates( dependencies, false ); - Map dependencyManagementUpdates = Collections.emptyMap(); - if ( processDependencyManagement ) + Map dependencyManagementUpdates = + processDependencyManagement ? getHelper().lookupDependenciesUpdates( dependencyManagement, false ) : + EMPTY_MAP; + + if ( onlyUpgradable ) { - dependencyManagementUpdates = getHelper().lookupDependenciesUpdates( dependencyManagement, false ); + dependencyUpdates = filter( dependencyUpdates, e -> e.getVersions().length > 1 ); + dependencyManagementUpdates = filter( dependencyManagementUpdates, e -> e.getVersions().length > 1 ); } + for ( String format : formats ) { if ( "html".equals( format ) ) { DependencyUpdatesRenderer renderer = - new DependencyUpdatesRenderer( sink, getI18n(), getOutputName(), locale, dependencyUpdates, - dependencyManagementUpdates ); + new DependencyUpdatesRenderer( sink, getI18n(), getOutputName(), locale, dependencyUpdates, + dependencyManagementUpdates ); renderer.render(); } else if ( "xml".equals( format ) ) { - File outputDir = new File(getProject().getBuild().getDirectory()); - if (!outputDir.exists()) + File outputDir = new File( getProject().getBuild().getDirectory() ); + if ( !outputDir.exists() ) { - outputDir.mkdirs(); + if ( !outputDir.mkdirs() ) + { + throw new MavenReportException( "Could not create output directory" ); + } } - String outputFile = - outputDir.getAbsolutePath() + File.separator + getOutputName() + ".xml"; + String outputFile = outputDir.getAbsolutePath() + File.separator + getOutputName() + ".xml"; DependencyUpdatesXmlRenderer xmlGenerator = - new DependencyUpdatesXmlRenderer( dependencyUpdates, dependencyManagementUpdates, outputFile ); + new DependencyUpdatesXmlRenderer( dependencyUpdates, dependencyManagementUpdates, + outputFile ); xmlGenerator.render(); } } @@ -184,42 +219,17 @@ else if ( "xml".equals( format ) ) } /** - * Returns a set of dependencies where the dependencies which are defined in the dependency management section have - * been filtered out. + * Compares two dependencies with each other * - * @param dependencies The set of dependencies. - * @param dependencyManagement The set of dependencies from the dependency management section. - * @return A new set of dependencies which are from the set of dependencies but not from the set of dependency - * management dependencies. - * @since 1.0-beta-1 + * @return true if the two dependcies match */ - private static Set removeDependencyManagment( Set dependencies, Set dependencyManagement ) + private boolean match( Dependency dep, Dependency dmDep ) { - Set result = new TreeSet<>( new DependencyComparator() ); - for ( Dependency c : dependencies ) - { - boolean matched = false; - Iterator j = dependencyManagement.iterator(); - while ( !matched && j.hasNext() ) - { - Dependency t = j.next(); - if ( StringUtils.equals( t.getGroupId(), c.getGroupId() ) - && StringUtils.equals( t.getArtifactId(), c.getArtifactId() ) - && ( t.getScope() == null || StringUtils.equals( t.getScope(), c.getScope() ) ) - && ( t.getClassifier() == null || StringUtils.equals( t.getClassifier(), c.getClassifier() ) ) - && ( c.getVersion() == null || t.getVersion() == null - || StringUtils.equals( t.getVersion(), c.getVersion() ) ) ) - { - matched = true; - break; - } - } - if ( !matched ) - { - result.add( c ); - } - } - return result; + return StringUtils.equals( dmDep.getGroupId(), dep.getGroupId() ) && StringUtils.equals( dmDep.getArtifactId(), + dep.getArtifactId() ) && ( dmDep.getScope() == null || StringUtils.equals( dmDep.getScope(), + dep.getScope() ) ) && ( dmDep.getClassifier() == null || StringUtils.equals( dmDep.getClassifier(), + dep.getClassifier() ) ) && ( dep.getVersion() == null || dmDep.getVersion() == null + || StringUtils.equals( dmDep.getVersion(), dep.getVersion() ) ); } /** diff --git a/src/main/java/org/codehaus/mojo/versions/PluginUpdatesReport.java b/src/main/java/org/codehaus/mojo/versions/PluginUpdatesReport.java index 516d7bb73b..5ced4f1903 100644 --- a/src/main/java/org/codehaus/mojo/versions/PluginUpdatesReport.java +++ b/src/main/java/org/codehaus/mojo/versions/PluginUpdatesReport.java @@ -19,6 +19,12 @@ * under the License. */ +import java.io.File; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; + import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; import org.apache.maven.doxia.sink.Sink; import org.apache.maven.model.Plugin; @@ -27,13 +33,8 @@ import org.apache.maven.plugins.annotations.ResolutionScope; import org.apache.maven.reporting.MavenReportException; import org.codehaus.mojo.versions.utils.PluginComparator; -import org.codehaus.plexus.util.StringUtils; -import java.io.File; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import static org.codehaus.mojo.versions.utils.MiscUtils.filter; /** * Generates a report of available updates for the plugins of a project. @@ -41,18 +42,34 @@ * @author Stephen Connolly * @since 1.0-beta-1 */ -@Mojo( name = "plugin-updates-report", requiresProject = true, requiresDependencyResolution = ResolutionScope.RUNTIME, threadSafe = true ) -public class PluginUpdatesReport - extends AbstractVersionsReport +@Mojo( name = "plugin-updates-report", requiresDependencyResolution = ResolutionScope.RUNTIME, + threadSafe = true ) +public class PluginUpdatesReport extends AbstractVersionsReport { /** * Report formats (html and/or xml). HTML by default. - * */ @Parameter( property = "pluginUpdatesReportFormats", defaultValue = "html" ) private String[] formats = new String[] { "html" }; + /** + * If true, only shows the subsection of the pluginManagement artifacts that + * are actually used in the project's plugin graph. false by default. + * + * @since 2.12 + */ + @Parameter( property = "onlyProjectPlugins", defaultValue = "false" ) + protected boolean onlyProjectPlugins; + + /** + * If true, only shows upgradable plugins in the report. false by default. + * + * @since 2.12 + */ + @Parameter( property = "onlyUpgradable", defaultValue = "false" ) + protected boolean onlyUpgradable; + /** * {@inheritDoc} */ @@ -72,24 +89,23 @@ public boolean canGenerateReport() private boolean haveBuildPluginManagementPlugins() { return getProject().getBuild() != null && getProject().getBuild().getPluginManagement() != null - && getProject().getBuild().getPluginManagement().getPlugins() != null - && !getProject().getBuild().getPluginManagement().getPlugins().isEmpty(); + && getProject().getBuild().getPluginManagement().getPlugins() != null && !getProject().getBuild() + .getPluginManagement().getPlugins().isEmpty(); } private boolean haveBuildPlugins() { return getProject().getBuild() != null && getProject().getBuild().getPlugins() != null - && !getProject().getBuild().getPlugins().isEmpty(); + && !getProject().getBuild().getPlugins().isEmpty(); } /** * generates an empty report in case there are no sources to generate a report with * * @param locale the locale to generate the report for. - * @param sink the report formatting tool + * @param sink the report formatting tool */ - protected void doGenerateReport( Locale locale, Sink sink ) - throws MavenReportException + protected void doGenerateReport( Locale locale, Sink sink ) throws MavenReportException { Set pluginManagement = new TreeSet<>( new PluginComparator() ); if ( haveBuildPluginManagementPlugins() ) @@ -103,34 +119,57 @@ protected void doGenerateReport( Locale locale, Sink sink ) plugins.addAll( getProject().getBuild().getPlugins() ); } - plugins = removePluginManagment( plugins, pluginManagement ); + PluginComparator comparator = new PluginComparator(); + if ( !onlyProjectPlugins ) + { + // Retains only plugins not present in pluginManagement + plugins.removeIf( plugin -> pluginManagement.stream() + .anyMatch( pmPlugin -> comparator.compare( plugin, pmPlugin ) == 0 ) ); + } + else + { + // Retain only plugins in pluginManagement that are also present in plugins + pluginManagement.removeIf( + pmPlugin -> plugins.stream().noneMatch( plugin -> comparator.compare( plugin, pmPlugin ) == 0 ) ); + } try { Map pluginUpdates = - getHelper().lookupPluginsUpdates( plugins, getAllowSnapshots() ); + getHelper().lookupPluginsUpdates( plugins, getAllowSnapshots() ); Map pluginManagementUpdates = - getHelper().lookupPluginsUpdates( pluginManagement, getAllowSnapshots() ); + getHelper().lookupPluginsUpdates( pluginManagement, getAllowSnapshots() ); + + if ( onlyUpgradable ) + { + pluginUpdates = + filter( pluginUpdates, plugin -> plugin.getArtifactVersions().getVersions().length > 1 ); + pluginManagementUpdates = filter( pluginManagementUpdates, + plugin -> plugin.getArtifactVersions().getVersions().length > 1 ); + } + for ( String format : formats ) { if ( "html".equals( format ) ) { PluginUpdatesRenderer renderer = - new PluginUpdatesRenderer( sink, getI18n(), getOutputName(), locale, pluginUpdates, - pluginManagementUpdates ); + new PluginUpdatesRenderer( sink, getI18n(), getOutputName(), locale, pluginUpdates, + pluginManagementUpdates ); renderer.render(); } else if ( "xml".equals( format ) ) { - File outputDir = new File(getProject().getBuild().getDirectory()); - if (!outputDir.exists()) + File outputDir = new File( getProject().getBuild().getDirectory() ); + if ( !outputDir.exists() ) { - outputDir.mkdirs(); + if ( !outputDir.mkdirs() ) + { + throw new MavenReportException( "Could not create output directory" ); + } } - String outputFile = - outputDir.getAbsolutePath() + File.separator + getOutputName() + ".xml"; + String outputFile = outputDir.getAbsolutePath() + File.separator + getOutputName() + ".xml"; PluginUpdatesXmlRenderer xmlGenerator = - new PluginUpdatesXmlRenderer( pluginUpdates, pluginManagementUpdates, outputFile ); + new PluginUpdatesXmlRenderer( pluginUpdates, pluginManagementUpdates, outputFile ); xmlGenerator.render(); } } @@ -141,39 +180,6 @@ else if ( "xml".equals( format ) ) } } - /** - * Returns a set of dependencies where the dependencies which are defined in the dependency management section have - * been filtered out. - * - * @param plugins The set of dependencies. - * @param pluginManagement The set of dependencies from the dependency management section. - * @return A new set of dependencies which are from the set of dependencies but not from the set of dependency - * management dependencies. - * @since 1.0-beta-1 - */ - private static Set removePluginManagment( Set plugins, Set pluginManagement ) - { - Set result = new TreeSet<>( new PluginComparator() ); - for ( Plugin c : plugins ) - { - boolean matched = false; - for ( Plugin t : pluginManagement ) - { - if ( StringUtils.equals( t.getGroupId(), c.getGroupId() ) - && StringUtils.equals( t.getArtifactId(), c.getArtifactId() ) ) - { - matched = true; - break; - } - } - if ( !matched ) - { - result.add( c ); - } - } - return result; - } - /** * {@inheritDoc} */ diff --git a/src/main/java/org/codehaus/mojo/versions/utils/MiscUtils.java b/src/main/java/org/codehaus/mojo/versions/utils/MiscUtils.java new file mode 100644 index 0000000000..215ae598a9 --- /dev/null +++ b/src/main/java/org/codehaus/mojo/versions/utils/MiscUtils.java @@ -0,0 +1,28 @@ +package org.codehaus.mojo.versions.utils; + +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Miscellaneous utility class. + */ +public class MiscUtils +{ + /** + * Filters a given map leaving only elements fulfilling a predicate. Does not change the input map, + * the filtered map is returned as output. + * + * @param map input map to be filtered + * @param predicate predicate for element comparison + * @param key type + * @param value type + * @return map such that every element comforms with the predicate + */ + public static Map filter( Map map, + Function predicate ) + { + return map.entrySet().stream().filter( e -> predicate.apply( e.getValue() ) ) + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ); + } +} diff --git a/src/test/java/org/codehaus/mojo/versions/DependencyUpdatesReportTest.java b/src/test/java/org/codehaus/mojo/versions/DependencyUpdatesReportTest.java new file mode 100644 index 0000000000..07bfa7e71b --- /dev/null +++ b/src/test/java/org/codehaus/mojo/versions/DependencyUpdatesReportTest.java @@ -0,0 +1,234 @@ +package org.codehaus.mojo.versions; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.doxia.module.xhtml5.Xhtml5SinkFactory; +import org.apache.maven.doxia.sink.SinkFactory; +import org.apache.maven.doxia.tools.SiteTool; +import org.apache.maven.doxia.tools.SiteToolException; +import org.apache.maven.model.Dependency; +import org.apache.maven.model.DependencyManagement; +import org.apache.maven.model.Model; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.repository.RepositorySystem; +import org.codehaus.plexus.i18n.I18N; +import org.junit.Test; + +import static java.util.Collections.EMPTY_LIST; +import static org.apache.maven.artifact.Artifact.SCOPE_COMPILE; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Basic tests for {@linkplain DependencyUpdatesReport}. + * + * @author Andrzej Jarmoniuk + */ +public class DependencyUpdatesReportTest +{ + private static class TestDependencyUpdatesReport extends DependencyUpdatesReport + { + /** + *

Mocks some Plexus components to speed up test execution.

+ *

Note: these components could just as well be injected using + * org.codehaus.plexus.PlexusTestCase.lookup, + * but that method greatly slows down test execution.

+ * + * @see Testing + * Plexus Components + */ + private void mockPlexusComponents() + { + i18n = mock( I18N.class ); + when( i18n.getString( anyString(), any(), anyString() ) ).thenAnswer( + invocation -> invocation.getArgument( 2 ) ); + + repositorySystem = mock( RepositorySystem.class ); + when( repositorySystem.createDependencyArtifact( any( Dependency.class ) ) ).thenAnswer( invocation -> { + Dependency dependency = invocation.getArgument( 0 ); + return new DefaultArtifact( dependency.getGroupId(), dependency.getArtifactId(), + dependency.getVersion(), dependency.getScope(), dependency.getType(), + dependency.getClassifier(), null ); + } ); + + Artifact skinArtifact = mock( Artifact.class ); + when( skinArtifact.getId() ).thenReturn( "" ); + siteTool = mock( SiteTool.class ); + try + { + when( siteTool.getSkinArtifactFromRepository( any(), any(), any() ) ).thenReturn( skinArtifact ); + } + catch ( SiteToolException e ) + { + throw new RuntimeException( e ); + } + } + + public TestDependencyUpdatesReport( List dependencies, + List originalDependencyManagement, + List dependencyManagement ) + { + mockPlexusComponents(); + + project = new MavenProject(); + project.setDependencies( dependencies ); + + project.setOriginalModel( new Model() ); + project.getOriginalModel().setDependencyManagement( new DependencyManagement() ); + project.getOriginalModel().getDependencyManagement().setDependencies( originalDependencyManagement ); + + project.getModel().setDependencyManagement( new DependencyManagement() ); + project.getModel().getDependencyManagement().setDependencies( dependencyManagement ); + + artifactMetadataSource = mock( ArtifactMetadataSource.class ); + try + { + when( artifactMetadataSource.retrieveAvailableVersions( any( Artifact.class ), any(), any() ) ).then( + invocation -> { + Artifact artifact = invocation.getArgument( 0 ); + if ( "artifactA".equals( artifact.getArtifactId() ) && "1.0.0".equals( + artifact.getVersion() ) ) + { + return Arrays.asList( new DefaultArtifactVersion( artifact.getVersion() ), + new DefaultArtifactVersion( "2.0.0" ) ); + } + if ( "artifactB".equals( artifact.getArtifactId() ) && "1.0.0".equals( + artifact.getVersion() ) ) + { + return Arrays.asList( new DefaultArtifactVersion( artifact.getVersion() ), + new DefaultArtifactVersion( "1.1.0" ) ); + } + return Collections.singletonList( new DefaultArtifactVersion( artifact.getVersion() ) ); + } ); + } + catch ( ArtifactMetadataRetrievalException e ) + { + throw new RuntimeException( e ); + } + } + } + + private Dependency dependencyOf( String artifactId ) + { + return DependencyBuilder.dependencyWith( "groupA", artifactId, "1.0.0", "default", "pom", SCOPE_COMPILE ); + } + + @Test + public void testOnlyUpgradableDependencies() throws IOException, MavenReportException + { + OutputStream os = new ByteArrayOutputStream(); + SinkFactory sinkFactory = new Xhtml5SinkFactory(); + new TestDependencyUpdatesReport( + Arrays.asList( dependencyOf( "artifactA" ), dependencyOf( "artifactB" ), + dependencyOf( "artifactC" ) ), EMPTY_LIST, EMPTY_LIST ) + { + { + onlyUpgradable = true; + } + }.generate( sinkFactory.createSink( os ), sinkFactory, Locale.getDefault() ); + + String output = os.toString(); + assertThat( output, allOf( containsString( "artifactA" ), containsString( "artifactB" ) ) ); + assertThat( output, not( containsString( "artifactC" ) ) ); + } + + @Test + public void testOnlyUpgradableWithOriginalDependencyManagement() throws IOException, MavenReportException + { + OutputStream os = new ByteArrayOutputStream(); + SinkFactory sinkFactory = new Xhtml5SinkFactory(); + new TestDependencyUpdatesReport( EMPTY_LIST, + Arrays.asList( dependencyOf( "artifactA" ), dependencyOf( "artifactB" ), + dependencyOf( "artifactC" ) ), EMPTY_LIST ) + { + { + processDependencyManagement = true; + onlyUpgradable = true; + } + }.generate( sinkFactory.createSink( os ), sinkFactory, Locale.getDefault() ); + + String output = os.toString(); + assertThat( output, allOf( containsString( "artifactA" ), containsString( "artifactB" ) ) ); + assertThat( output, not( containsString( "artifactC" ) ) ); + } + + @Test + public void testOnlyUpgradableWithTransitiveDependencyManagement() throws IOException, MavenReportException + { + OutputStream os = new ByteArrayOutputStream(); + SinkFactory sinkFactory = new Xhtml5SinkFactory(); + new TestDependencyUpdatesReport( EMPTY_LIST, EMPTY_LIST, + Arrays.asList( dependencyOf( "artifactA" ), dependencyOf( "artifactB" ), + dependencyOf( "artifactC" ) ) ) + { + { + processDependencyManagement = true; + processDependencyManagementTransitive = true; + onlyUpgradable = true; + } + }.generate( sinkFactory.createSink( os ), sinkFactory, Locale.getDefault() ); + + String output = os.toString(); + assertThat( output, allOf( containsString( "artifactA" ), containsString( "artifactB" ) ) ); + assertThat( output, not( containsString( "artifactC" ) ) ); + } + + @Test + public void testOnlyProjectDependencies() throws IOException, MavenReportException + { + OutputStream os = new ByteArrayOutputStream(); + SinkFactory sinkFactory = new Xhtml5SinkFactory(); + new TestDependencyUpdatesReport( Collections.singletonList( dependencyOf( "artifactA" ) ), + Arrays.asList( dependencyOf( "artifactA" ), dependencyOf( "artifactB" ), + dependencyOf( "artifactC" ) ), EMPTY_LIST ) + { + { + processDependencyManagement = true; + onlyProjectDependencies = true; + } + }.generate( sinkFactory.createSink( os ), sinkFactory, Locale.getDefault() ); + + String output = os.toString(); + assertThat( output, containsString( "artifactA" ) ); + assertThat( output, not( anyOf( containsString( "artifactB" ), containsString( "artifactC" ) ) ) ); + } +} \ No newline at end of file diff --git a/src/test/java/org/codehaus/mojo/versions/PluginUpdatesReportTest.java b/src/test/java/org/codehaus/mojo/versions/PluginUpdatesReportTest.java new file mode 100644 index 0000000000..97176dbae7 --- /dev/null +++ b/src/test/java/org/codehaus/mojo/versions/PluginUpdatesReportTest.java @@ -0,0 +1,211 @@ +package org.codehaus.mojo.versions; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.artifact.DefaultArtifact; +import org.apache.maven.artifact.metadata.ArtifactMetadataRetrievalException; +import org.apache.maven.artifact.metadata.ArtifactMetadataSource; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.doxia.module.xhtml5.Xhtml5SinkFactory; +import org.apache.maven.doxia.sink.SinkFactory; +import org.apache.maven.doxia.tools.SiteTool; +import org.apache.maven.doxia.tools.SiteToolException; +import org.apache.maven.model.Build; +import org.apache.maven.model.Plugin; +import org.apache.maven.model.PluginManagement; +import org.apache.maven.project.MavenProject; +import org.apache.maven.reporting.MavenReportException; +import org.apache.maven.repository.RepositorySystem; +import org.codehaus.plexus.i18n.I18N; +import org.junit.Test; + +import static java.util.Collections.EMPTY_LIST; +import static org.apache.maven.artifact.Artifact.SCOPE_RUNTIME; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.anyOf; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Basic tests for {@linkplain PluginUpdatesReport}. + * + * @author Andrzej Jarmoniuk + */ +public class PluginUpdatesReportTest +{ + private static class TestPluginUpdatesReport extends PluginUpdatesReport + { + /** + *

Mocks some Plexus components to speed up test execution.

+ *

Note: these components could just as well be injected using + * org.codehaus.plexus.PlexusTestCase.lookup, + * but that method greatly slows down test execution.

+ * + * @see Testing + * Plexus Components + */ + private void mockPlexusComponents() + { + i18n = mock( I18N.class ); + when( i18n.getString( anyString(), any(), anyString() ) ).thenAnswer( + invocation -> invocation.getArgument( 2 ) ); + + repositorySystem = mock( RepositorySystem.class ); + when( repositorySystem.createPluginArtifact( any( Plugin.class ) ) ).thenAnswer( invocation -> { + Plugin plugin = invocation.getArgument( 0 ); + return new DefaultArtifact( plugin.getGroupId(), plugin.getArtifactId(), plugin.getVersion(), + SCOPE_RUNTIME, "maven-plugin", "jar", null ); + } ); + + Artifact skinArtifact = mock( Artifact.class ); + when( skinArtifact.getId() ).thenReturn( "" ); + siteTool = mock( SiteTool.class ); + try + { + when( siteTool.getSkinArtifactFromRepository( any(), any(), any() ) ).thenReturn( skinArtifact ); + } + catch ( SiteToolException e ) + { + throw new RuntimeException( e ); + } + } + + public TestPluginUpdatesReport( List plugins, List pluginManagement ) + { + mockPlexusComponents(); + + project = new MavenProject(); + project.setBuild( new Build() ); + project.getBuild().setPlugins( plugins ); + project.getBuild().setPluginManagement( new PluginManagement() ); + project.getBuild().getPluginManagement().setPlugins( pluginManagement ); + + artifactMetadataSource = mock( ArtifactMetadataSource.class ); + try + { + when( artifactMetadataSource.retrieveAvailableVersions( any( Artifact.class ), any(), any() ) ).then( + invocation -> { + Artifact artifact = invocation.getArgument( 0 ); + if ( "artifactA".equals( artifact.getArtifactId() ) && "1.0.0".equals( + artifact.getVersion() ) ) + { + return Arrays.asList( new DefaultArtifactVersion( artifact.getVersion() ), + new DefaultArtifactVersion( "2.0.0" ) ); + } + if ( "artifactB".equals( artifact.getArtifactId() ) && "1.0.0".equals( + artifact.getVersion() ) ) + { + return Arrays.asList( new DefaultArtifactVersion( artifact.getVersion() ), + new DefaultArtifactVersion( "1.1.0" ) ); + } + return Collections.singletonList( new DefaultArtifactVersion( artifact.getVersion() ) ); + } ); + } + catch ( ArtifactMetadataRetrievalException e ) + { + throw new RuntimeException( e ); + } + } + } + + private static Plugin pluginOf( String artifactId ) + { + return new Plugin() + { + { + setGroupId( "defaultGroup" ); + setArtifactId( artifactId ); + setVersion( "1.0.0" ); + } + }; + } + + @Test + public void testOnlyUpgradablePlugins() throws IOException, MavenReportException + { + OutputStream os = new ByteArrayOutputStream(); + SinkFactory sinkFactory = new Xhtml5SinkFactory(); + new TestPluginUpdatesReport( Arrays.asList( pluginOf( "artifactA" ), pluginOf( "artifactB" ), + pluginOf( "artifactC" ) ), EMPTY_LIST ) + { + { + onlyUpgradable = true; + } + }.generate( sinkFactory.createSink( os ), sinkFactory, Locale.getDefault() ); + + String output = os.toString(); + assertThat( output, allOf( containsString( "artifactA" ), containsString( "artifactB" ) ) ); + assertThat( output, not( containsString( "artifactC" ) ) ); + } + + @Test + public void testOnlyUpgradableWithPluginManagement() throws IOException, MavenReportException + { + OutputStream os = new ByteArrayOutputStream(); + SinkFactory sinkFactory = new Xhtml5SinkFactory(); + new TestPluginUpdatesReport( EMPTY_LIST, + Arrays.asList( pluginOf( "artifactA" ), pluginOf( "artifactB" ), + pluginOf( "artifactC" ) ) ) + { + { + onlyUpgradable = true; + } + }.generate( sinkFactory.createSink( os ), sinkFactory, Locale.getDefault() ); + + String output = os.toString(); + assertThat( output, allOf( containsString( "artifactA" ), containsString( "artifactB" ) ) ); + assertThat( output, not( containsString( "artifactC" ) ) ); + } + + @Test + public void testOnlyProjectPlugins() throws IOException, MavenReportException + { + OutputStream os = new ByteArrayOutputStream(); + SinkFactory sinkFactory = new Xhtml5SinkFactory(); + new TestPluginUpdatesReport( Collections.singletonList( pluginOf( "artifactA" ) ), + Arrays.asList( pluginOf( "artifactA" ), pluginOf( "artifactB" ), + pluginOf( "artifactC" ) ) ) + { + { + onlyUpgradable = true; + onlyProjectPlugins = true; + } + }.generate( sinkFactory.createSink( os ), sinkFactory, Locale.getDefault() ); + + String output = os.toString(); + assertThat( output, containsString( "artifactA" ) ); + assertThat( output, not( anyOf( containsString( "artifactB" ), containsString( "artifactC" ) ) ) ); + } +} \ No newline at end of file