You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2022/07/29 09:30:48 UTC

[maven] branch master updated: [MNG-7443] Implement consistent logging between optional projects and optional profiles

This is an automated email from the ASF dual-hosted git repository.

michaelo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git


The following commit(s) were added to refs/heads/master by this push:
     new a53a1aa23 [MNG-7443] Implement consistent logging between optional projects and optional profiles
a53a1aa23 is described below

commit a53a1aa232bc383baf055d884a7c66319d10d404
Author: Giovanni van der Schelde <Gi...@infosupport.com>
AuthorDate: Tue Mar 29 15:28:53 2022 +0200

    [MNG-7443] Implement consistent logging between optional projects and optional profiles
    
    This closes #701
---
 .../main/java/org/apache/maven/DefaultMaven.java   |  38 +++-
 .../apache/maven/graph/DefaultGraphBuilder.java    | 103 +---------
 .../org/apache/maven/graph/ProjectSelector.java    | 158 +++++++++++++++
 .../apache/maven/graph/ProjectSelectorTest.java    | 221 +++++++++++++++++++++
 4 files changed, 418 insertions(+), 102 deletions(-)

diff --git a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
index 14b613899..9792c219c 100644
--- a/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
+++ b/maven-core/src/main/java/org/apache/maven/DefaultMaven.java
@@ -29,8 +29,10 @@ import org.apache.maven.execution.MavenExecutionRequest;
 import org.apache.maven.execution.MavenExecutionResult;
 import org.apache.maven.execution.MavenSession;
 import org.apache.maven.execution.ProfileActivation;
+import org.apache.maven.execution.ProjectActivation;
 import org.apache.maven.execution.ProjectDependencyGraph;
 import org.apache.maven.graph.GraphBuilder;
+import org.apache.maven.graph.ProjectSelector;
 import org.apache.maven.internal.aether.DefaultRepositorySystemSessionFactory;
 import org.apache.maven.lifecycle.LifecycleExecutionException;
 import org.apache.maven.internal.aether.MavenChainedWorkspaceReader;
@@ -54,6 +56,7 @@ import org.eclipse.aether.RepositorySystemSession;
 import org.eclipse.aether.repository.WorkspaceReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.slf4j.helpers.MessageFormatter;
 
 import javax.inject.Inject;
 import javax.inject.Named;
@@ -107,6 +110,8 @@ public class DefaultMaven
 
     private final SuperPomProvider superPomProvider;
 
+    private final ProjectSelector projectSelector;
+
     @Inject
     public DefaultMaven(
             ProjectBuilder projectBuilder,
@@ -132,6 +137,7 @@ public class DefaultMaven
         this.buildResumptionAnalyzer = buildResumptionAnalyzer;
         this.buildResumptionDataRepository = buildResumptionDataRepository;
         this.superPomProvider = superPomProvider;
+        this.projectSelector = new ProjectSelector(); // if necessary switch to DI
     }
 
     @Override
@@ -323,6 +329,7 @@ public class DefaultMaven
 
             lifecycleStarter.execute( session );
 
+            validateOptionalProjects( request, session );
             validateOptionalProfiles( session, request.getProfileActivation() );
 
             if ( session.getResult().hasExceptions() )
@@ -623,14 +630,30 @@ public class DefaultMaven
 
         if ( !notFoundRequiredProfiles.isEmpty() )
         {
-            final String message = String.format(
-                    "The requested profiles [%s] could not be activated or deactivated because they do not exist.",
-                    String.join( ", ", notFoundRequiredProfiles )
-            );
+            // Use SLF4J formatter for consistency with warnings reported by logger
+            final String message = MessageFormatter.format(
+                    "The requested profiles {} could not be activated or deactivated because they do not"
+                            + " exist.", notFoundRequiredProfiles ).getMessage();
             addExceptionToResult( session.getResult(), new MissingProfilesException( message ) );
         }
     }
 
+    /**
+     * Check whether any of the requested optional projects were not activated or deactivated.
+     * @param request the {@link MavenExecutionRequest}.
+     * @param session the {@link MavenSession}.
+     */
+    private void validateOptionalProjects( MavenExecutionRequest request, MavenSession session )
+    {
+        final ProjectActivation projectActivation = request.getProjectActivation();
+        final Set<String> allOptionalSelectors = new HashSet<>();
+        allOptionalSelectors.addAll( projectActivation.getOptionalActiveProjectSelectors() );
+        allOptionalSelectors.addAll( projectActivation.getRequiredActiveProjectSelectors() );
+        // We intentionally ignore the results of this method.
+        // As a side effect it will log the optional projects that could not be resolved.
+        projectSelector.getOptionalProjectsBySelectors( request, session.getAllProjects(), allOptionalSelectors );
+    }
+
     /**
      * Check whether any of the requested optional profiles were not activated or deactivated.
      * @param session the Maven session.
@@ -650,11 +673,8 @@ public class DefaultMaven
 
         if ( !notFoundOptionalProfiles.isEmpty() )
         {
-            final String message = String.format(
-                    "The requested optional profiles [%s] could not be activated or deactivated because they "
-                            + "do not exist.", String.join( ", ", notFoundOptionalProfiles )
-            );
-            logger.info( message );
+            logger.info( "The requested optional profiles {} could not be activated or deactivated because they do not"
+                            + " exist.", notFoundOptionalProfiles );
         }
     }
 
diff --git a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java
index ff863df4a..0d4f6ca45 100644
--- a/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java
+++ b/maven-core/src/main/java/org/apache/maven/graph/DefaultGraphBuilder.java
@@ -25,10 +25,8 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Optional;
 import java.util.Set;
 
 import javax.inject.Inject;
@@ -73,6 +71,7 @@ public class DefaultGraphBuilder
     private final PomlessCollectionStrategy pomlessCollectionStrategy;
     private final MultiModuleCollectionStrategy multiModuleCollectionStrategy;
     private final RequestPomCollectionStrategy requestPomCollectionStrategy;
+    private final ProjectSelector projectSelector;
 
     @Inject
     public DefaultGraphBuilder( BuildResumptionDataRepository buildResumptionDataRepository,
@@ -84,6 +83,7 @@ public class DefaultGraphBuilder
         this.pomlessCollectionStrategy = pomlessCollectionStrategy;
         this.multiModuleCollectionStrategy = multiModuleCollectionStrategy;
         this.requestPomCollectionStrategy = requestPomCollectionStrategy;
+        this.projectSelector = new ProjectSelector(); // if necessary switch to DI
     }
 
     @Override
@@ -186,9 +186,9 @@ public class DefaultGraphBuilder
         {
             Set<MavenProject> selectedProjects = new HashSet<>( requiredSelectors.size() + optionalSelectors.size() );
             selectedProjects.addAll(
-                    getProjectsBySelectors( request, allSortedProjects, requiredSelectors, true ) );
+                    projectSelector.getRequiredProjectsBySelectors( request, allSortedProjects, requiredSelectors ) );
             selectedProjects.addAll(
-                    getProjectsBySelectors( request, allSortedProjects, optionalSelectors, false ) );
+                    projectSelector.getOptionalProjectsBySelectors( request, allSortedProjects, optionalSelectors ) );
 
             // it can be empty when an optional project is missing from the reactor, fallback to returning all projects
             if ( !selectedProjects.isEmpty() )
@@ -206,46 +206,6 @@ public class DefaultGraphBuilder
         return result;
     }
 
-    private Set<MavenProject> getProjectsBySelectors( MavenExecutionRequest request, List<MavenProject> projects,
-                                                      Set<String> projectSelectors, boolean required )
-            throws MavenExecutionException
-    {
-        Set<MavenProject> selectedProjects = new LinkedHashSet<>();
-        File reactorDirectory = getReactorDirectory( request );
-
-        for ( String selector : projectSelectors )
-        {
-            Optional<MavenProject> optSelectedProject = projects.stream()
-                    .filter( project -> isMatchingProject( project, selector, reactorDirectory ) )
-                    .findFirst();
-            if ( !optSelectedProject.isPresent() )
-            {
-                String message = "Could not find the selected project in the reactor: " + selector;
-                if ( required )
-                {
-                    throw new MavenExecutionException( message, request.getPom() );
-                }
-                else
-                {
-                    LOGGER.info( message );
-                    break;
-                }
-            }
-
-            MavenProject selectedProject = optSelectedProject.get();
-
-            selectedProjects.add( selectedProject );
-
-            List<MavenProject> children = selectedProject.getCollectedProjects();
-            if ( children != null && request.isRecursive() )
-            {
-                selectedProjects.addAll( children );
-            }
-        }
-
-        return selectedProjects;
-    }
-
     private List<MavenProject> trimResumedProjects( List<MavenProject> projects, ProjectDependencyGraph graph,
                                                     MavenExecutionRequest request )
             throws MavenExecutionException
@@ -254,12 +214,12 @@ public class DefaultGraphBuilder
 
         if ( StringUtils.isNotEmpty( request.getResumeFrom() ) )
         {
-            File reactorDirectory = getReactorDirectory( request );
+            File reactorDirectory = projectSelector.getBaseDirectoryFromRequest( request );
 
             String selector = request.getResumeFrom();
 
             MavenProject resumingFromProject = projects.stream()
-                    .filter( project -> isMatchingProject( project, selector, reactorDirectory ) )
+                    .filter( project -> projectSelector.isMatchingProject( project, selector, reactorDirectory ) )
                     .findFirst()
                     .orElseThrow( () -> new MavenExecutionException(
                             "Could not find project to resume reactor build from: " + selector + " vs "
@@ -286,8 +246,10 @@ public class DefaultGraphBuilder
         {
             Set<MavenProject> excludedProjects = new HashSet<>( requiredSelectors.size() + optionalSelectors.size() );
             List<MavenProject> allProjects = graph.getAllProjects();
-            excludedProjects.addAll( getProjectsBySelectors( request, allProjects, requiredSelectors, true ) );
-            excludedProjects.addAll( getProjectsBySelectors( request, allProjects, optionalSelectors, false ) );
+            excludedProjects.addAll(
+                    projectSelector.getRequiredProjectsBySelectors( request, allProjects, requiredSelectors ) );
+            excludedProjects.addAll(
+                    projectSelector.getOptionalProjectsBySelectors( request, allProjects, optionalSelectors ) );
 
             result = new ArrayList<>( projects );
             result.removeAll( excludedProjects );
@@ -398,51 +360,6 @@ public class DefaultGraphBuilder
         return projectNames.toString();
     }
 
-    private boolean isMatchingProject( MavenProject project, String selector, File reactorDirectory )
-    {
-        // [groupId]:artifactId
-        if ( selector.indexOf( ':' ) >= 0 )
-        {
-            String id = ':' + project.getArtifactId();
-
-            if ( id.equals( selector ) )
-            {
-                return true;
-            }
-
-            id = project.getGroupId() + id;
-
-            return id.equals( selector );
-        }
-
-        // relative path, e.g. "sub", "../sub" or "."
-        else if ( reactorDirectory != null )
-        {
-            File selectedProject = new File( new File( reactorDirectory, selector ).toURI().normalize() );
-
-            if ( selectedProject.isFile() )
-            {
-                return selectedProject.equals( project.getFile() );
-            }
-            else if ( selectedProject.isDirectory() )
-            {
-                return selectedProject.equals( project.getBasedir() );
-            }
-        }
-
-        return false;
-    }
-
-    private File getReactorDirectory( MavenExecutionRequest request )
-    {
-        if ( request.getBaseDirectory() != null )
-        {
-            return new File( request.getBaseDirectory() );
-        }
-
-        return null;
-    }
-
     // ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
     //
     // Project collection
diff --git a/maven-core/src/main/java/org/apache/maven/graph/ProjectSelector.java b/maven-core/src/main/java/org/apache/maven/graph/ProjectSelector.java
new file mode 100644
index 000000000..bc81abc2b
--- /dev/null
+++ b/maven-core/src/main/java/org/apache/maven/graph/ProjectSelector.java
@@ -0,0 +1,158 @@
+package org.apache.maven.graph;
+
+/*
+ * 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 org.apache.maven.MavenExecutionException;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.project.MavenProject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Utility class to extract {@link MavenProject} from the project graph during the execution phase based on optional or
+ * required selectors.
+ */
+public final class ProjectSelector
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger( ProjectSelector.class );
+
+    public Set<MavenProject> getRequiredProjectsBySelectors( MavenExecutionRequest request, List<MavenProject> projects,
+                                                             Set<String> projectSelectors )
+            throws MavenExecutionException
+    {
+        Set<MavenProject> selectedProjects = new LinkedHashSet<>();
+        File baseDirectory = getBaseDirectoryFromRequest( request );
+        for ( String selector : projectSelectors )
+        {
+            Optional<MavenProject> optSelectedProject =
+                    findOptionalProjectBySelector( projects, baseDirectory, selector );
+            if ( !optSelectedProject.isPresent() )
+            {
+                String message = "Could not find the selected project in the reactor: " + selector;
+                throw new MavenExecutionException( message, request.getPom() );
+            }
+
+            MavenProject selectedProject = optSelectedProject.get();
+
+            selectedProjects.add( selectedProject );
+            selectedProjects.addAll( getChildProjects( selectedProject, request ) );
+        }
+
+        return selectedProjects;
+    }
+
+    public Set<MavenProject> getOptionalProjectsBySelectors( MavenExecutionRequest request, List<MavenProject> projects,
+                                                             Set<String> projectSelectors )
+    {
+        Set<MavenProject> resolvedOptionalProjects = new LinkedHashSet<>();
+        Set<String> unresolvedOptionalSelectors = new HashSet<>();
+        File baseDirectory = getBaseDirectoryFromRequest( request );
+        for ( String selector : projectSelectors )
+        {
+            Optional<MavenProject> optSelectedProject =
+                    findOptionalProjectBySelector( projects, baseDirectory, selector );
+            if ( optSelectedProject.isPresent() )
+            {
+                resolvedOptionalProjects.add( optSelectedProject.get() );
+                resolvedOptionalProjects.addAll( getChildProjects( optSelectedProject.get(), request ) );
+            }
+            else
+            {
+                unresolvedOptionalSelectors.add( selector );
+            }
+        }
+
+        if ( !unresolvedOptionalSelectors.isEmpty() )
+        {
+            LOGGER.info( "The requested optional projects {} do not exist.", unresolvedOptionalSelectors );
+        }
+
+        return resolvedOptionalProjects;
+    }
+
+    private List<MavenProject> getChildProjects( MavenProject parent, MavenExecutionRequest request )
+    {
+        final List<MavenProject> children = parent.getCollectedProjects();
+        if ( children != null && request.isRecursive() )
+        {
+            return children;
+        }
+        else
+        {
+            return new ArrayList<>();
+        }
+    }
+
+    private Optional<MavenProject> findOptionalProjectBySelector( List<MavenProject> projects, File reactorDirectory,
+                                                                  String selector )
+    {
+        return projects.stream()
+                .filter( project -> isMatchingProject( project, selector, reactorDirectory ) )
+                .findFirst();
+    }
+
+    File getBaseDirectoryFromRequest( MavenExecutionRequest request )
+    {
+        return request.getBaseDirectory() != null ? new File( request.getBaseDirectory() ) : null;
+    }
+
+    boolean isMatchingProject( MavenProject project, String selector, File reactorDirectory )
+    {
+        // [groupId]:artifactId
+        if ( selector.contains( ":" ) )
+        {
+            String id = ':' + project.getArtifactId();
+
+            if ( id.equals( selector ) )
+            {
+                return true;
+            }
+
+            id = project.getGroupId() + id;
+
+            return id.equals( selector );
+        }
+
+        // relative path, e.g. "sub", "../sub" or "."
+        else if ( reactorDirectory != null )
+        {
+            File selectedProject = new File( new File( reactorDirectory, selector ).toURI().normalize() );
+
+            if ( selectedProject.isFile() )
+            {
+                return selectedProject.equals( project.getFile() );
+            }
+            else if ( selectedProject.isDirectory() )
+            {
+                return selectedProject.equals( project.getBasedir() );
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/maven-core/src/test/java/org/apache/maven/graph/ProjectSelectorTest.java b/maven-core/src/test/java/org/apache/maven/graph/ProjectSelectorTest.java
new file mode 100644
index 000000000..34bb4453f
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/graph/ProjectSelectorTest.java
@@ -0,0 +1,221 @@
+package org.apache.maven.graph;
+
+/*
+ * 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 org.apache.maven.MavenExecutionException;
+import org.apache.maven.execution.MavenExecutionRequest;
+import org.apache.maven.project.MavenProject;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EmptySource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ProjectSelectorTest
+{
+    private final ProjectSelector sut = new ProjectSelector();
+    private final MavenExecutionRequest mavenExecutionRequest = mock( MavenExecutionRequest.class );
+
+    @Test
+    void getBaseDirectoryFromRequestWhenDirectoryIsNullReturnNull()
+    {
+        when( mavenExecutionRequest.getBaseDirectory() ).thenReturn( null );
+
+        final File baseDirectoryFromRequest = sut.getBaseDirectoryFromRequest( mavenExecutionRequest );
+
+        assertThat( baseDirectoryFromRequest, nullValue() );
+    }
+
+    @Test
+    void getBaseDirectoryFromRequestWhenDirectoryIsValidReturnFile()
+    {
+        when( mavenExecutionRequest.getBaseDirectory() ).thenReturn( "path/to/file" );
+
+        final File baseDirectoryFromRequest = sut.getBaseDirectoryFromRequest( mavenExecutionRequest );
+
+        assertThat( baseDirectoryFromRequest, notNullValue() );
+        assertThat( baseDirectoryFromRequest.getPath(), is( new File( "path/to/file" ).getPath() ) );
+    }
+
+    @ParameterizedTest
+    @ValueSource( strings = {":wrong-selector", "wrong-selector"} )
+    @EmptySource
+    void isMatchingProjectNoMatchOnSelectorReturnsFalse( String selector )
+    {
+        final boolean result = sut.isMatchingProject( createMavenProject("maven-core" ), selector, null );
+        assertThat( result, is( false ) );
+    }
+
+    @ParameterizedTest
+    @ValueSource( strings = {":maven-core", "org.apache.maven:maven-core"} )
+    void isMatchingProjectMatchOnSelectorReturnsTrue( String selector )
+    {
+        final boolean result = sut.isMatchingProject( createMavenProject("maven-core" ), selector, null );
+        assertThat( result, is( true ) );
+    }
+
+    @Test
+    void isMatchingProjectMatchOnFileReturnsTrue() throws IOException
+    {
+        final File tempFile = File.createTempFile( "maven-core-unit-test-pom", ".xml" );
+        final String selector = tempFile.getName();
+        final MavenProject mavenProject = createMavenProject("maven-core" );
+        mavenProject.setFile( tempFile );
+
+        final boolean result = sut.isMatchingProject( mavenProject, selector, tempFile.getParentFile() );
+
+        tempFile.delete();
+        assertThat( result, is( true ) );
+    }
+
+    @Test
+    void isMatchingProjectMatchOnDirectoryReturnsTrue(@TempDir File tempDir)
+    {
+        String selector = "maven-core";
+        final File tempProjectDir = new File( tempDir, "maven-core" );
+        tempProjectDir.mkdir();
+        final MavenProject mavenProject = createMavenProject("maven-core" );
+        mavenProject.setFile( new File( tempProjectDir, "some-file.xml" ) );
+
+        final boolean result = sut.isMatchingProject( mavenProject, selector, tempDir );
+
+        tempProjectDir.delete();
+        assertThat( result, is( true ) );
+    }
+
+    @Test
+    void getOptionalProjectsBySelectorsReturnsMatches()
+    {
+        final HashSet<String> selectors = new HashSet<>();
+        selectors.add( ":maven-core" );
+        selectors.add( ":optional" );
+
+        final MavenProject mavenProject = createMavenProject("maven-core" );
+        final List<MavenProject> listOfProjects = Collections.singletonList( mavenProject );
+
+        final Set<MavenProject> optionalProjectsBySelectors =
+                sut.getOptionalProjectsBySelectors( mavenExecutionRequest, listOfProjects, selectors );
+
+        assertThat( optionalProjectsBySelectors.size(), is( 1 ) );
+        assertThat( optionalProjectsBySelectors, contains( mavenProject ) );
+    }
+
+    @Test
+    void getRequiredProjectsBySelectorsThrowsMavenExecutionException()
+    {
+        final HashSet<String> selectors = new HashSet<>();
+        selectors.add( ":maven-core" );
+        selectors.add( ":required" );
+
+        final MavenProject mavenProject = createMavenProject("maven-core" );
+        final List<MavenProject> listOfProjects = Collections.singletonList( mavenProject );
+
+        final MavenExecutionException exception = assertThrows( MavenExecutionException.class,
+                () -> sut.getRequiredProjectsBySelectors( mavenExecutionRequest, listOfProjects, selectors ) );
+        assertThat( exception.getMessage(), containsString( "Could not find" ) );
+        assertThat( exception.getMessage(), containsString( ":required" ) );
+    }
+
+    @Test
+    void getRequiredProjectsBySelectorsReturnsProject() throws MavenExecutionException
+    {
+        final HashSet<String> selectors = new HashSet<>();
+        selectors.add( ":maven-core" );
+
+        final MavenProject mavenProject = createMavenProject("maven-core" );
+        final List<MavenProject> listOfProjects = Collections.singletonList( mavenProject );
+
+        final Set<MavenProject> requiredProjectsBySelectors =
+                sut.getRequiredProjectsBySelectors( mavenExecutionRequest, listOfProjects, selectors );
+
+        assertThat( requiredProjectsBySelectors.size(), is( 1 ) );
+        assertThat( requiredProjectsBySelectors, contains( mavenProject ) );
+    }
+
+    @Test
+    void getRequiredProjectsBySelectorsReturnsProjectWithChildProjects() throws MavenExecutionException
+    {
+        when( mavenExecutionRequest.isRecursive() ).thenReturn( true );
+
+        final HashSet<String> selectors = new HashSet<>();
+        selectors.add( ":maven-core" );
+
+        final MavenProject mavenProject = createMavenProject("maven-core" );
+        final MavenProject child = createMavenProject("maven-core-child" );
+        mavenProject.setCollectedProjects( Collections.singletonList( child ) );
+        final List<MavenProject> listOfProjects = Collections.singletonList( mavenProject );
+
+        final Set<MavenProject> requiredProjectsBySelectors =
+                sut.getRequiredProjectsBySelectors( mavenExecutionRequest, listOfProjects, selectors );
+
+        assertThat( requiredProjectsBySelectors.size(), is( 2 ) );
+        assertThat( requiredProjectsBySelectors, contains( mavenProject, child ) );
+    }
+
+    @Test
+    void getOptionalProjectsBySelectorsReturnsProjectWithChildProjects()
+    {
+        when( mavenExecutionRequest.isRecursive() ).thenReturn( true );
+
+        final HashSet<String> selectors = new HashSet<>();
+        selectors.add( ":maven-core" );
+
+        final MavenProject mavenProject = createMavenProject("maven-core" );
+        final MavenProject child = createMavenProject("maven-core-child" );
+        mavenProject.setCollectedProjects( Collections.singletonList( child ) );
+        final List<MavenProject> listOfProjects = Collections.singletonList( mavenProject );
+
+        final Set<MavenProject> optionalProjectsBySelectors =
+                sut.getOptionalProjectsBySelectors( mavenExecutionRequest, listOfProjects, selectors );
+
+        assertThat( optionalProjectsBySelectors.size(), is( 2 ) );
+        assertThat( optionalProjectsBySelectors, contains( mavenProject, child ) );
+    }
+
+    private MavenProject createMavenProject(String artifactId )
+    {
+        MavenProject mavenProject = new MavenProject();
+        mavenProject.setGroupId( "org.apache.maven" );
+        mavenProject.setArtifactId( artifactId );
+        mavenProject.setVersion( "1.0" );
+        mavenProject.setFile( new File( artifactId, "some-dir" ) );
+        mavenProject.setCollectedProjects( new ArrayList<>() );
+        return mavenProject;
+    }
+
+}