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