View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.graph;
20  
21  import java.io.File;
22  import java.nio.file.Paths;
23  import java.util.ArrayList;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.stream.Collectors;
29  import java.util.stream.Stream;
30  
31  import org.apache.maven.MavenExecutionException;
32  import org.apache.maven.api.services.model.ModelProcessor;
33  import org.apache.maven.execution.BuildResumptionDataRepository;
34  import org.apache.maven.execution.MavenExecutionRequest;
35  import org.apache.maven.execution.MavenSession;
36  import org.apache.maven.execution.ProjectActivation;
37  import org.apache.maven.execution.ProjectDependencyGraph;
38  import org.apache.maven.impl.model.DefaultModelProcessor;
39  import org.apache.maven.model.Dependency;
40  import org.apache.maven.model.Parent;
41  import org.apache.maven.model.building.Result;
42  import org.apache.maven.project.MavenProject;
43  import org.apache.maven.project.ProjectBuilder;
44  import org.apache.maven.project.ProjectBuildingException;
45  import org.apache.maven.project.ProjectBuildingRequest;
46  import org.apache.maven.project.ProjectBuildingResult;
47  import org.apache.maven.project.collector.DefaultProjectsSelector;
48  import org.apache.maven.project.collector.MultiModuleCollectionStrategy;
49  import org.apache.maven.project.collector.PomlessCollectionStrategy;
50  import org.apache.maven.project.collector.ProjectsSelector;
51  import org.apache.maven.project.collector.RequestPomCollectionStrategy;
52  import org.junit.jupiter.api.BeforeEach;
53  import org.junit.jupiter.api.Test;
54  import org.junit.jupiter.params.ParameterizedTest;
55  import org.junit.jupiter.params.provider.Arguments;
56  import org.junit.jupiter.params.provider.MethodSource;
57  
58  import static java.util.Arrays.asList;
59  import static java.util.Collections.emptyList;
60  import static java.util.Collections.singletonList;
61  import static java.util.function.Function.identity;
62  import static java.util.stream.Collectors.toList;
63  import static org.apache.maven.execution.MavenExecutionRequest.REACTOR_MAKE_DOWNSTREAM;
64  import static org.apache.maven.execution.MavenExecutionRequest.REACTOR_MAKE_UPSTREAM;
65  import static org.apache.maven.graph.DefaultGraphBuilderTest.ScenarioBuilder.scenario;
66  import static org.junit.jupiter.api.Assertions.assertEquals;
67  import static org.junit.jupiter.api.Assertions.assertFalse;
68  import static org.junit.jupiter.api.Assertions.assertTrue;
69  import static org.mockito.ArgumentMatchers.any;
70  import static org.mockito.ArgumentMatchers.anyBoolean;
71  import static org.mockito.ArgumentMatchers.anyList;
72  import static org.mockito.Mockito.mock;
73  import static org.mockito.Mockito.when;
74  
75  class DefaultGraphBuilderTest {
76      /*
77      The multi-module structure in this project is displayed as follows:
78  
79      module-parent
80      └─── module-independent     (without parent declaration)
81           module-a
82           module-b               (depends on module-a)
83           module-c
84           └─── module-c-1
85                module-c-2        (depends on module-b)
86           module-d               (packaging is bom)
87       */
88      private static final String GROUP_ID = "unittest";
89      private static final String PARENT_MODULE = "module-parent";
90      private static final String INDEPENDENT_MODULE = "module-independent";
91      private static final String MODULE_A = "module-a";
92      private static final String MODULE_B = "module-b";
93      private static final String MODULE_C = "module-c";
94      private static final String MODULE_D = "module-d";
95      private static final String MODULE_C_1 = "module-c-1";
96      private static final String MODULE_C_2 = "module-c-2";
97  
98      private DefaultGraphBuilder graphBuilder;
99  
100     private final ProjectBuilder projectBuilder = mock(ProjectBuilder.class);
101     private final MavenSession session = mock(MavenSession.class);
102     private final MavenExecutionRequest mavenExecutionRequest = mock(MavenExecutionRequest.class);
103 
104     private final ProjectsSelector projectsSelector = new DefaultProjectsSelector(projectBuilder);
105 
106     // Not using mocks for these strategies - a mock would just copy the actual implementation.
107 
108     private final ModelProcessor modelProcessor = new DefaultModelProcessor(null, List.of());
109     private final PomlessCollectionStrategy pomlessCollectionStrategy = new PomlessCollectionStrategy(projectBuilder);
110     private final MultiModuleCollectionStrategy multiModuleCollectionStrategy =
111             new MultiModuleCollectionStrategy(modelProcessor, projectsSelector);
112     private final RequestPomCollectionStrategy requestPomCollectionStrategy =
113             new RequestPomCollectionStrategy(projectsSelector);
114 
115     private Map<String, MavenProject> artifactIdProjectMap;
116 
117     public static Stream<Arguments> parameters() {
118         return Stream.of(
119                 scenario("Full reactor in order")
120                         .expectResult(
121                                 PARENT_MODULE,
122                                 MODULE_C,
123                                 MODULE_C_1,
124                                 MODULE_A,
125                                 MODULE_B,
126                                 MODULE_C_2,
127                                 INDEPENDENT_MODULE),
128                 scenario("Selected project").activeRequiredProjects(MODULE_B).expectResult(MODULE_B),
129                 scenario("Selected aggregator project (including child modules)")
130                         .activeRequiredProjects(MODULE_C)
131                         .expectResult(MODULE_C, MODULE_C_1, MODULE_C_2),
132                 scenario("Selected aggregator project with non-recursive")
133                         .activeRequiredProjects(MODULE_C)
134                         .nonRecursive()
135                         .expectResult(MODULE_C),
136                 scenario("Selected optional project")
137                         .activeOptionalProjects(MODULE_B)
138                         .expectResult(MODULE_B),
139                 scenario("Selected missing optional project")
140                         .activeOptionalProjects("non-existing-module")
141                         .expectResult(
142                                 PARENT_MODULE,
143                                 MODULE_C,
144                                 MODULE_C_1,
145                                 MODULE_A,
146                                 MODULE_B,
147                                 MODULE_C_2,
148                                 INDEPENDENT_MODULE),
149                 scenario("Selected missing optional and required project")
150                         .activeOptionalProjects("non-existing-module")
151                         .activeRequiredProjects(MODULE_B)
152                         .expectResult(MODULE_B),
153                 scenario("Excluded project")
154                         .inactiveRequiredProjects(MODULE_B)
155                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE),
156                 scenario("Excluded optional project")
157                         .inactiveOptionalProjects(MODULE_B)
158                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE),
159                 scenario("Excluded missing optional project")
160                         .inactiveOptionalProjects("non-existing-module")
161                         .expectResult(
162                                 PARENT_MODULE,
163                                 MODULE_C,
164                                 MODULE_C_1,
165                                 MODULE_A,
166                                 MODULE_B,
167                                 MODULE_C_2,
168                                 INDEPENDENT_MODULE),
169                 scenario("Excluded missing optional and required project")
170                         .inactiveOptionalProjects("non-existing-module")
171                         .inactiveRequiredProjects(MODULE_B)
172                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE),
173                 scenario("Excluded aggregator project with non-recursive")
174                         .inactiveRequiredProjects(MODULE_C)
175                         .nonRecursive()
176                         .expectResult(PARENT_MODULE, MODULE_C_1, MODULE_A, MODULE_B, MODULE_C_2, INDEPENDENT_MODULE),
177                 scenario("Selected and excluded same project")
178                         .activeRequiredProjects(MODULE_A)
179                         .inactiveRequiredProjects(MODULE_A)
180                         .expectResult(MavenExecutionException.class, "empty reactor"),
181                 scenario("Excluded aggregator, but selected child")
182                         .activeRequiredProjects(MODULE_C_1)
183                         .inactiveRequiredProjects(MODULE_C)
184                         .expectResult(MavenExecutionException.class, "empty reactor"),
185                 scenario("Project selected with different selector resolves to same project")
186                         .activeRequiredProjects(GROUP_ID + ":" + MODULE_A)
187                         .inactiveRequiredProjects(MODULE_A)
188                         .expectResult(MavenExecutionException.class, "empty reactor"),
189                 scenario("Selected and excluded same project, but also selected another project")
190                         .activeRequiredProjects(MODULE_A, MODULE_B)
191                         .inactiveRequiredProjects(MODULE_A)
192                         .expectResult(MODULE_B),
193                 scenario("Selected missing project as required and as optional")
194                         .activeRequiredProjects("non-existing-module")
195                         .activeOptionalProjects("non-existing-module")
196                         .expectResult(MavenExecutionException.class, "do not exist"),
197                 scenario("Resuming from project")
198                         .resumeFrom(MODULE_B)
199                         .expectResult(MODULE_B, MODULE_C_2, INDEPENDENT_MODULE),
200                 scenario("Selected project with also make dependencies")
201                         .activeRequiredProjects(MODULE_C_2)
202                         .makeBehavior(REACTOR_MAKE_UPSTREAM)
203                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2),
204                 scenario("Selected project with also make dependents")
205                         .activeRequiredProjects(MODULE_B)
206                         .makeBehavior(REACTOR_MAKE_DOWNSTREAM)
207                         .expectResult(MODULE_B, MODULE_C_2),
208                 scenario("Resuming from project with also make dependencies")
209                         .makeBehavior(REACTOR_MAKE_UPSTREAM)
210                         .resumeFrom(MODULE_C_2)
211                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2, INDEPENDENT_MODULE),
212                 scenario("Selected project with resume from and also make dependency (MNG-4960 IT#1)")
213                         .activeRequiredProjects(MODULE_C_2)
214                         .resumeFrom(MODULE_B)
215                         .makeBehavior(REACTOR_MAKE_UPSTREAM)
216                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_A, MODULE_B, MODULE_C_2),
217                 scenario("Selected project with resume from and also make dependent (MNG-4960 IT#2)")
218                         .activeRequiredProjects(MODULE_B)
219                         .resumeFrom(MODULE_C_2)
220                         .makeBehavior(REACTOR_MAKE_DOWNSTREAM)
221                         .expectResult(MODULE_C_2),
222                 scenario("Excluding an also make dependency from selectedProject does take its transitive dependency")
223                         .activeRequiredProjects(MODULE_C_2)
224                         .inactiveRequiredProjects(MODULE_B)
225                         .makeBehavior(REACTOR_MAKE_UPSTREAM)
226                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_A, MODULE_C_2),
227                 scenario("Excluding a project also excludes its children")
228                         .inactiveRequiredProjects(MODULE_C)
229                         .expectResult(PARENT_MODULE, MODULE_A, MODULE_B, INDEPENDENT_MODULE),
230                 scenario("Excluding an also make dependency from resumeFrom does take its transitive dependency")
231                         .resumeFrom(MODULE_C_2)
232                         .inactiveRequiredProjects(MODULE_B)
233                         .makeBehavior(REACTOR_MAKE_UPSTREAM)
234                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_A, MODULE_C_2, INDEPENDENT_MODULE),
235                 scenario("Resume from exclude project downstream")
236                         .resumeFrom(MODULE_A)
237                         .inactiveRequiredProjects(MODULE_B)
238                         .expectResult(MODULE_A, MODULE_C_2, INDEPENDENT_MODULE),
239                 scenario("Exclude the project we are resuming from (as proposed in MNG-6676)")
240                         .resumeFrom(MODULE_B)
241                         .inactiveRequiredProjects(MODULE_B)
242                         .expectResult(MODULE_C_2, INDEPENDENT_MODULE),
243                 scenario("Selected projects in wrong order are resumed correctly in order")
244                         .activeRequiredProjects(MODULE_C_2, MODULE_B, MODULE_A)
245                         .resumeFrom(MODULE_B)
246                         .expectResult(MODULE_B, MODULE_C_2),
247                 scenario("Duplicate projects are filtered out")
248                         .activeRequiredProjects(MODULE_A, MODULE_A)
249                         .expectResult(MODULE_A),
250                 scenario("Select reactor by specific pom")
251                         .requestedPom(MODULE_C)
252                         .expectResult(MODULE_C, MODULE_C_1, MODULE_C_2),
253                 scenario("Select reactor by specific pom with also make dependencies")
254                         .requestedPom(MODULE_C)
255                         .makeBehavior(REACTOR_MAKE_UPSTREAM)
256                         .expectResult(PARENT_MODULE, MODULE_C, MODULE_C_1, MODULE_A, MODULE_B, MODULE_C_2),
257                 scenario("Select reactor by specific pom with also make dependents")
258                         .requestedPom(MODULE_B)
259                         .makeBehavior(REACTOR_MAKE_DOWNSTREAM)
260                         .expectResult(MODULE_B, MODULE_C_2));
261     }
262 
263     interface ExpectedResult {}
264 
265     static class SelectedProjectsResult implements ExpectedResult {
266         final List<String> projectNames;
267 
268         SelectedProjectsResult(List<String> projectSelectors) {
269             this.projectNames = projectSelectors;
270         }
271     }
272 
273     static class ExceptionThrown implements ExpectedResult {
274         final Class<? extends Throwable> expected;
275         final String partOfMessage;
276 
277         ExceptionThrown(final Class<? extends Throwable> expected, final String partOfMessage) {
278             this.expected = expected;
279             this.partOfMessage = partOfMessage;
280         }
281     }
282 
283     @ParameterizedTest
284     @MethodSource("parameters")
285     @SuppressWarnings("checkstyle:ParameterNumber")
286     void testGetReactorProjects(
287             String parameterDescription,
288             List<String> parameterActiveRequiredProjects,
289             List<String> parameterActiveOptionalProjects,
290             List<String> parameterInactiveRequiredProjects,
291             List<String> parameterInactiveOptionalProjects,
292             String parameterResumeFrom,
293             String parameterMakeBehavior,
294             ExpectedResult parameterExpectedResult,
295             File parameterRequestedPom,
296             boolean parameterRecursive) {
297         // Given
298         ProjectActivation projectActivation = new ProjectActivation();
299         parameterActiveRequiredProjects.forEach(projectActivation::activateRequiredProject);
300         parameterActiveOptionalProjects.forEach(projectActivation::activateOptionalProject);
301         parameterInactiveRequiredProjects.forEach(projectActivation::deactivateRequiredProject);
302         parameterInactiveOptionalProjects.forEach(projectActivation::deactivateOptionalProject);
303 
304         when(mavenExecutionRequest.getRootDirectory()).thenReturn(Paths.get("."));
305         when(mavenExecutionRequest.getProjectActivation()).thenReturn(projectActivation);
306         when(mavenExecutionRequest.getMakeBehavior()).thenReturn(parameterMakeBehavior);
307         when(mavenExecutionRequest.getPom()).thenReturn(parameterRequestedPom);
308         when(mavenExecutionRequest.isRecursive()).thenReturn(parameterRecursive);
309         if (parameterResumeFrom != null && !parameterResumeFrom.isEmpty()) {
310             when(mavenExecutionRequest.getResumeFrom()).thenReturn(":" + parameterResumeFrom);
311         }
312 
313         // When
314         Result<ProjectDependencyGraph> result = graphBuilder.build(session);
315 
316         // Then
317         if (parameterExpectedResult instanceof SelectedProjectsResult selectedProjectsResult) {
318             assertFalse(result.hasErrors(), "Expected result not to have errors");
319             List<String> expectedProjectNames = selectedProjectsResult.projectNames;
320             List<MavenProject> actualReactorProjects = result.get().getSortedProjects();
321             List<MavenProject> expectedReactorProjects =
322                     expectedProjectNames.stream().map(artifactIdProjectMap::get).collect(toList());
323             assertEquals(expectedReactorProjects, actualReactorProjects, parameterDescription);
324         } else {
325             assertTrue(result.hasErrors(), "Expected result to have errors");
326             Class<? extends Throwable> expectedException = ((ExceptionThrown) parameterExpectedResult).expected;
327             String partOfMessage = ((ExceptionThrown) parameterExpectedResult).partOfMessage;
328 
329             assertEquals(1, ((Collection) result.getProblems()).size());
330             result.getProblems().forEach(p -> {
331                 assertTrue(expectedException.isInstance(p.getException()));
332                 assertTrue(p.getException().getMessage().contains(partOfMessage));
333             });
334         }
335     }
336 
337     @Test
338     void testProcessPackagingAttribute() throws ProjectBuildingException {
339         graphBuilder = new DefaultGraphBuilder(
340                 mock(BuildResumptionDataRepository.class),
341                 pomlessCollectionStrategy,
342                 multiModuleCollectionStrategy,
343                 requestPomCollectionStrategy);
344 
345         // Create projects
346         MavenProject projectParent = getMavenProject(PARENT_MODULE);
347         MavenProject projectModuleD = getMavenProject(MODULE_D, projectParent, "bom");
348 
349         projectParent.setCollectedProjects(singletonList(projectModuleD));
350 
351         // Set up needed mocks
352         when(session.getRequest()).thenReturn(mavenExecutionRequest);
353         when(session.getProjects()).thenReturn(null); // needed, otherwise it will be an empty list by default
354         when(mavenExecutionRequest.getProjectBuildingRequest()).thenReturn(mock(ProjectBuildingRequest.class));
355         List<ProjectBuildingResult> projectBuildingResults =
356                 createProjectBuildingResultMocks(Stream.of(projectParent, projectModuleD)
357                         .collect(Collectors.toMap(MavenProject::getArtifactId, identity()))
358                         .values());
359         when(projectBuilder.build(anyList(), anyBoolean(), any(ProjectBuildingRequest.class)))
360                 .thenReturn(projectBuildingResults);
361 
362         ProjectActivation projectActivation = new ProjectActivation();
363 
364         when(mavenExecutionRequest.getProjectActivation()).thenReturn(projectActivation);
365         when(mavenExecutionRequest.getPom()).thenReturn(new File(PARENT_MODULE, "pom.xml"));
366 
367         Result<ProjectDependencyGraph> result = graphBuilder.build(session);
368 
369         assertFalse(result.hasErrors(), "Expected result not to have errors");
370         List<MavenProject> actualReactorProjects = result.get().getSortedProjects();
371         assertEquals(2, actualReactorProjects.size());
372         assertEquals("pom", actualReactorProjects.get(1).getPackaging());
373     }
374 
375     @BeforeEach
376     void before() throws Exception {
377         graphBuilder = new DefaultGraphBuilder(
378                 mock(BuildResumptionDataRepository.class),
379                 pomlessCollectionStrategy,
380                 multiModuleCollectionStrategy,
381                 requestPomCollectionStrategy);
382 
383         // Create projects
384         MavenProject projectParent = getMavenProject(PARENT_MODULE);
385         MavenProject projectIndependentModule = getMavenProject(INDEPENDENT_MODULE);
386         MavenProject projectModuleA = getMavenProject(MODULE_A, projectParent);
387         MavenProject projectModuleB = getMavenProject(MODULE_B, projectParent);
388         MavenProject projectModuleC = getMavenProject(MODULE_C, projectParent);
389         MavenProject projectModuleC1 = getMavenProject(MODULE_C_1, projectModuleC);
390         MavenProject projectModuleC2 = getMavenProject(MODULE_C_2, projectModuleC);
391 
392         artifactIdProjectMap = Stream.of(
393                         projectParent,
394                         projectIndependentModule,
395                         projectModuleA,
396                         projectModuleB,
397                         projectModuleC,
398                         projectModuleC1,
399                         projectModuleC2)
400                 .collect(Collectors.toMap(MavenProject::getArtifactId, identity()));
401 
402         // Set dependencies and modules
403         projectModuleB.setDependencies(singletonList(toDependency(projectModuleA)));
404         projectModuleC2.setDependencies(singletonList(toDependency(projectModuleB)));
405         projectParent.setCollectedProjects(asList(
406                 projectIndependentModule,
407                 projectModuleA,
408                 projectModuleB,
409                 projectModuleC,
410                 projectModuleC1,
411                 projectModuleC2));
412         projectModuleC.setCollectedProjects(asList(projectModuleC1, projectModuleC2));
413 
414         // Set up needed mocks
415         when(session.getRequest()).thenReturn(mavenExecutionRequest);
416         when(session.getProjects()).thenReturn(null); // needed, otherwise it will be an empty list by default
417         when(mavenExecutionRequest.getProjectBuildingRequest()).thenReturn(mock(ProjectBuildingRequest.class));
418         List<ProjectBuildingResult> projectBuildingResults =
419                 createProjectBuildingResultMocks(artifactIdProjectMap.values());
420         when(projectBuilder.build(anyList(), anyBoolean(), any(ProjectBuildingRequest.class)))
421                 .thenReturn(projectBuildingResults);
422         when(mavenExecutionRequest.getRootDirectory()).thenReturn(null);
423     }
424 
425     private MavenProject getMavenProject(String artifactId, MavenProject parentProject) {
426         MavenProject project = getMavenProject(artifactId);
427         Parent parent = new Parent();
428         parent.setGroupId(parentProject.getGroupId());
429         parent.setArtifactId(parentProject.getArtifactId());
430         project.getModel().setParent(parent);
431         return project;
432     }
433 
434     private MavenProject getMavenProject(String artifactId) {
435         MavenProject mavenProject = new MavenProject();
436         mavenProject.setGroupId(GROUP_ID);
437         mavenProject.setArtifactId(artifactId);
438         mavenProject.setVersion("1.0");
439         mavenProject.setPomFile(new File(artifactId, "pom.xml"));
440         mavenProject.setCollectedProjects(new ArrayList<>());
441         return mavenProject;
442     }
443 
444     private MavenProject getMavenProject(String artifactId, MavenProject parentProject, String packaging) {
445         MavenProject project = getMavenProject(artifactId);
446         Parent parent = new Parent();
447         parent.setGroupId(parentProject.getGroupId());
448         parent.setArtifactId(parentProject.getArtifactId());
449         project.getModel().setParent(parent);
450         project.setPackaging(packaging);
451         return project;
452     }
453 
454     private Dependency toDependency(MavenProject mavenProject) {
455         Dependency dependency = new Dependency();
456         dependency.setGroupId(mavenProject.getGroupId());
457         dependency.setArtifactId(mavenProject.getArtifactId());
458         dependency.setVersion(mavenProject.getVersion());
459         return dependency;
460     }
461 
462     private List<ProjectBuildingResult> createProjectBuildingResultMocks(Collection<MavenProject> projects) {
463         return projects.stream()
464                 .map(project -> {
465                     ProjectBuildingResult result = mock(ProjectBuildingResult.class);
466                     when(result.getProject()).thenReturn(project);
467                     return result;
468                 })
469                 .collect(toList());
470     }
471 
472     static class ScenarioBuilder {
473         private String description;
474         private List<String> activeRequiredProjects = emptyList();
475         private List<String> activeOptionalProjects = emptyList();
476         private List<String> inactiveRequiredProjects = emptyList();
477         private List<String> inactiveOptionalProjects = emptyList();
478         private String resumeFrom = "";
479         private String makeBehavior = "";
480         private File requestedPom = new File(PARENT_MODULE, "pom.xml");
481         private boolean recursive = true;
482 
483         private ScenarioBuilder() {}
484 
485         public static ScenarioBuilder scenario(String description) {
486             ScenarioBuilder scenarioBuilder = new ScenarioBuilder();
487             scenarioBuilder.description = description;
488             return scenarioBuilder;
489         }
490 
491         public ScenarioBuilder activeRequiredProjects(String... activeRequiredProjects) {
492             this.activeRequiredProjects = prependWithColonIfNeeded(activeRequiredProjects);
493             return this;
494         }
495 
496         public ScenarioBuilder activeOptionalProjects(String... activeOptionalProjects) {
497             this.activeOptionalProjects = prependWithColonIfNeeded(activeOptionalProjects);
498             return this;
499         }
500 
501         public ScenarioBuilder inactiveRequiredProjects(String... inactiveRequiredProjects) {
502             this.inactiveRequiredProjects = prependWithColonIfNeeded(inactiveRequiredProjects);
503             return this;
504         }
505 
506         public ScenarioBuilder inactiveOptionalProjects(String... inactiveOptionalProjects) {
507             this.inactiveOptionalProjects = prependWithColonIfNeeded(inactiveOptionalProjects);
508             return this;
509         }
510 
511         public ScenarioBuilder resumeFrom(String resumeFrom) {
512             this.resumeFrom = resumeFrom;
513             return this;
514         }
515 
516         public ScenarioBuilder makeBehavior(String makeBehavior) {
517             this.makeBehavior = makeBehavior;
518             return this;
519         }
520 
521         public ScenarioBuilder requestedPom(String requestedPom) {
522             this.requestedPom = new File(requestedPom, "pom.xml");
523             return this;
524         }
525 
526         public ScenarioBuilder nonRecursive() {
527             this.recursive = false;
528             return this;
529         }
530 
531         public Arguments expectResult(String... expectedReactorProjects) {
532             ExpectedResult expectedResult = new SelectedProjectsResult(asList(expectedReactorProjects));
533             return createTestArguments(expectedResult);
534         }
535 
536         public Arguments expectResult(Class<? extends Exception> expected, final String partOfMessage) {
537             ExpectedResult expectedResult = new ExceptionThrown(expected, partOfMessage);
538             return createTestArguments(expectedResult);
539         }
540 
541         private Arguments createTestArguments(ExpectedResult expectedResult) {
542             return Arguments.arguments(
543                     description,
544                     activeRequiredProjects,
545                     activeOptionalProjects,
546                     inactiveRequiredProjects,
547                     inactiveOptionalProjects,
548                     resumeFrom,
549                     makeBehavior,
550                     expectedResult,
551                     requestedPom,
552                     recursive);
553         }
554 
555         private List<String> prependWithColonIfNeeded(String[] selectors) {
556             return Arrays.stream(selectors).map(this::prependWithColonIfNeeded).collect(toList());
557         }
558 
559         private String prependWithColonIfNeeded(String selector) {
560             return selector.indexOf(':') == -1 ? ":" + selector : selector;
561         }
562     }
563 }