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