From f04c3ec387324851331740318677d1c049b0d798 Mon Sep 17 00:00:00 2001
From: Andrzej Jarmoniuk
Date: Fri, 19 Aug 2022 21:53:36 +0200
Subject: [PATCH] Limit to only artifacts that have updates and are in the
dependency graph (#589)
Added `onlyUpgradable`, which will restrict the reports to only show upgradable artifacts, and `onlyProjectDependencies`, `onlyProjectPlugins`, which will restrict the graph of demendencyManagement/pluginManagement artifacts to the graph used in the project.
Additionally, some refactoring and improvements.
---
.../mojo/versions/AbstractVersionsReport.java | 29 +-
.../versions/DependencyUpdatesReport.java | 181 ++++++------
.../mojo/versions/PluginUpdatesReport.java | 126 +++++----
.../mojo/versions/utils/MiscUtils.java | 28 ++
.../versions/DependencyUpdatesReportTest.java | 263 ++++++++++++++++++
.../versions/PluginUpdatesReportTest.java | 225 +++++++++++++++
6 files changed, 679 insertions(+), 173 deletions(-)
create mode 100644 src/main/java/org/codehaus/mojo/versions/utils/MiscUtils.java
create mode 100644 src/test/java/org/codehaus/mojo/versions/DependencyUpdatesReportTest.java
create mode 100644 src/test/java/org/codehaus/mojo/versions/PluginUpdatesReportTest.java
diff --git a/src/main/java/org/codehaus/mojo/versions/AbstractVersionsReport.java b/src/main/java/org/codehaus/mojo/versions/AbstractVersionsReport.java
index 8f9f32cc3..baf91b60a 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 6a5c14c7a..791ef57ab 100644
--- a/src/main/java/org/codehaus/mojo/versions/DependencyUpdatesReport.java
+++ b/src/main/java/org/codehaus/mojo/versions/DependencyUpdatesReport.java
@@ -20,8 +20,6 @@
*/
import java.io.File;
-import java.util.Collections;
-import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -36,7 +34,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 +44,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.
- *
+ * updates of the imported pom itself.
+ *
* @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 +113,101 @@ 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
+ @SuppressWarnings( "deprecation" )
+ 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,18 @@ 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 dependencies 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 dmDep.getGroupId().equals( dep.getGroupId() )
+ && dmDep.getArtifactId().equals( dep.getArtifactId() )
+ && ( dmDep.getScope() == null || dmDep.getScope().equals( dep.getScope() ) )
+ && ( dmDep.getClassifier() == null || dmDep.getClassifier().equals( dep.getClassifier() ) )
+ && ( dep.getVersion() == null || dmDep.getVersion() == null || dmDep.getVersion()
+ .equals( 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 516d7bb73..5ced4f190 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 000000000..215ae598a
--- /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 000000000..a5bf0765c
--- /dev/null
+++ b/src/test/java/org/codehaus/mojo/versions/DependencyUpdatesReportTest.java
@@ -0,0 +1,263 @@
+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.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 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
+ {
+ @SuppressWarnings( "deprecation" )
+ public TestDependencyUpdatesReport()
+ {
+ mockPlexusComponents();
+
+ project = new MavenProject();
+ project.setOriginalModel( new Model() );
+ project.getOriginalModel().setDependencyManagement( new DependencyManagement() );
+ project.getModel().setDependencyManagement( new 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 );
+ }
+ }
+
+ public TestDependencyUpdatesReport withDependencies( Dependency... dependencies )
+ {
+ project.setDependencies( Arrays.asList( dependencies ) );
+ return this;
+ }
+
+ public TestDependencyUpdatesReport withOriginalDependencyManagement(
+ Dependency... originalDependencyManagement )
+ {
+ project.getOriginalModel().getDependencyManagement()
+ .setDependencies( Arrays.asList( originalDependencyManagement ) );
+ return this;
+ }
+
+ public TestDependencyUpdatesReport withDependencyManagement( Dependency... dependencyManagement )
+ {
+ project.getModel().getDependencyManagement().setDependencies( Arrays.asList( dependencyManagement ) );
+ return this;
+ }
+
+ public TestDependencyUpdatesReport withOnlyUpgradable( boolean onlyUpgradable )
+ {
+ this.onlyUpgradable = onlyUpgradable;
+ return this;
+ }
+
+ public TestDependencyUpdatesReport withProcessDependencyManagement( boolean processDependencyManagement )
+ {
+ this.processDependencyManagement = processDependencyManagement;
+ return this;
+ }
+
+ public TestDependencyUpdatesReport withProcessDependencyManagementTransitive(
+ boolean processDependencyManagementTransitive )
+ {
+ this.processDependencyManagementTransitive = processDependencyManagementTransitive;
+ return this;
+ }
+
+ public TestDependencyUpdatesReport withOnlyProjectDependencies(
+ boolean onlyProjectDependencies )
+ {
+ this.onlyProjectDependencies = onlyProjectDependencies;
+ return this;
+ }
+
+ /**
+ * 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 );
+ }
+ }
+ }
+
+ private static 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()
+ .withOnlyUpgradable( true )
+ .withDependencies(
+ dependencyOf( "artifactA" ), dependencyOf( "artifactB" ),
+ dependencyOf( "artifactC" ) )
+ .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()
+ .withOriginalDependencyManagement( dependencyOf( "artifactA" ), dependencyOf( "artifactB" ),
+ dependencyOf( "artifactC" ) )
+ .withProcessDependencyManagement( true )
+ .withOnlyUpgradable( 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()
+ .withDependencyManagement(
+ dependencyOf( "artifactA" ), dependencyOf( "artifactB" ),
+ dependencyOf( "artifactC" ) )
+ .withProcessDependencyManagement( true )
+ .withProcessDependencyManagementTransitive( true )
+ .withOnlyUpgradable( 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()
+ .withDependencies( dependencyOf( "artifactA" ) )
+ .withDependencyManagement( dependencyOf( "artifactA" ), dependencyOf( "artifactB" ),
+ dependencyOf( "artifactC" ) )
+ .withProcessDependencyManagement( true )
+ .withOnlyProjectDependencies( 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 000000000..b893d557d
--- /dev/null
+++ b/src/test/java/org/codehaus/mojo/versions/PluginUpdatesReportTest.java
@@ -0,0 +1,225 @@
+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.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 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
+ {
+ @SuppressWarnings( "deprecation" )
+ public TestPluginUpdatesReport()
+ {
+ mockPlexusComponents();
+
+ project = new MavenProject();
+ project.setBuild( new Build() );
+ project.getBuild().setPluginManagement( new 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 );
+ }
+ }
+
+ public TestPluginUpdatesReport withPlugins( Plugin... plugins )
+ {
+ project.getBuild().setPlugins( Arrays.asList( plugins ) );
+ return this;
+ }
+
+ public TestPluginUpdatesReport withPluginManagement( Plugin... pluginManagement )
+ {
+ project.getBuild().getPluginManagement().setPlugins( Arrays.asList( pluginManagement ) );
+ return this;
+ }
+
+ public TestPluginUpdatesReport withOnlyUpgradable( boolean onlyUpgradable )
+ {
+ this.onlyUpgradable = onlyUpgradable;
+ return this;
+ }
+
+ public TestPluginUpdatesReport withOnlyProjectPlugins( boolean onlyProjectPlugins )
+ {
+ this.onlyProjectPlugins = onlyProjectPlugins;
+ return this;
+ }
+
+ /**
+ * 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 );
+ }
+ }
+ }
+
+ 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()
+ .withPlugins( pluginOf( "artifactA" ), pluginOf( "artifactB" ),
+ pluginOf( "artifactC" ) )
+ .withOnlyUpgradable( 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()
+ .withPluginManagement( pluginOf( "artifactA" ), pluginOf( "artifactB" ),
+ pluginOf( "artifactC" ) )
+ .withOnlyUpgradable( 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()
+ .withPlugins( pluginOf( "artifactA" ) )
+ .withPluginManagement( pluginOf( "artifactA" ), pluginOf( "artifactB" ),
+ pluginOf( "artifactC" ) )
+ .withOnlyUpgradable( true )
+ .withOnlyProjectPlugins( 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