/* * Copyright (c) 2014 - 2019 the original author or authors. * * Licensed 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. */ package com.github.ferstl.depgraph.dependency; import java.util.ArrayList; import java.util.EnumSet; import org.apache.maven.artifact.DefaultArtifact; import org.apache.maven.artifact.resolver.filter.ArtifactFilter; import org.apache.maven.project.DependencyResolutionRequest; import org.apache.maven.project.DependencyResolutionResult; import org.apache.maven.project.MavenProject; import org.apache.maven.project.ProjectBuildingRequest; import org.apache.maven.project.ProjectDependenciesResolver; import org.eclipse.aether.RepositorySystemSession; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentMatcher; import com.github.ferstl.depgraph.ToStringNodeIdRenderer; import com.github.ferstl.depgraph.graph.GraphBuilder; import static com.github.ferstl.depgraph.dependency.NodeResolution.INCLUDED; import static com.github.ferstl.depgraph.graph.GraphBuilderMatcher.emptyGraph; import static com.github.ferstl.depgraph.graph.GraphBuilderMatcher.hasNodesAndEdges; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * JUnit tests for {@link AggregatingGraphFactory}. * <p> * The {@link AggregatingGraphFactory} is a bit tricky to test since it cannot use a real instance of * {@link ProjectDependenciesResolver} because it would require other components of the Maven ecosystem. Instead, we use a * mock of the {@link ProjectDependenciesResolver} here. So to verify the correct behavior of * {@link AggregatingGraphFactory}, we can only check for the expected invocations of {@link ProjectDependenciesResolver} * instead of verifying the created graph. However, the parent/child relations are not a result of * {@link ProjectDependenciesResolver} invocations since they are directly created via the {@link GraphBuilder}. So these * relations need to be tested by verifying the {@link GraphBuilder}. * </p> */ class AggregatingGraphFactoryTest { private ArtifactFilter globalFilter; private ProjectDependenciesResolver dependenciesResolver; private MavenGraphAdapter adapter; private GraphBuilder<DependencyNode> graphBuilder; @BeforeEach void before() throws Exception { this.globalFilter = mock(ArtifactFilter.class); ArtifactFilter transitiveIncludeExcludeFilter = mock(ArtifactFilter.class); ArtifactFilter targetFilter = mock(ArtifactFilter.class); when(this.globalFilter.include(any())).thenReturn(true); when(transitiveIncludeExcludeFilter.include(any())).thenReturn(true); when(targetFilter.include(any())).thenReturn(true); DependencyResolutionResult dependencyResolutionResult = mock(DependencyResolutionResult.class); org.eclipse.aether.graph.DependencyNode dependencyNode = mock(org.eclipse.aether.graph.DependencyNode.class); when(dependencyResolutionResult.getDependencyGraph()).thenReturn(dependencyNode); this.dependenciesResolver = mock(ProjectDependenciesResolver.class); when(this.dependenciesResolver.resolve(any(DependencyResolutionRequest.class))).thenReturn(dependencyResolutionResult); this.adapter = new MavenGraphAdapter(this.dependenciesResolver, transitiveIncludeExcludeFilter, targetFilter, EnumSet.of(INCLUDED)); this.graphBuilder = GraphBuilder.create(ToStringNodeIdRenderer.INSTANCE); } /** * T. * <pre> * parent * - child1 * - child2 * include parents * </pre> */ @Test void moduleTree() { MavenProject parent = createMavenProject("parent"); createMavenProject("child1", parent); createMavenProject("child2", parent); AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false); graphFactory.createGraph(parent); assertThat(this.graphBuilder, hasNodesAndEdges( new String[]{ "\"groupId:parent:jar:version:compile\"", "\"groupId:child1:jar:version:compile\"", "\"groupId:child2:jar:version:compile\""}, new String[]{ "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]"})); } /** * . * <pre> * parent * - child1 * - child2 * exclude parents * </pre> */ @Test void excludeParentProjects() throws Exception { MavenProject parent = createMavenProject("parent"); createMavenProject("child1", parent); createMavenProject("child2", parent); AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, false, false); graphFactory.createGraph(parent); verify(this.dependenciesResolver, never()).resolve(argThat(projectName("parent"))); verify(this.dependenciesResolver).resolve(argThat(projectName("child1"))); verify(this.dependenciesResolver).resolve(argThat(projectName("child2"))); // There are no module nodes. So because of the mocked graph builder the graph must be empty here. assertThat(this.graphBuilder, emptyGraph()); } /** * . * <pre> * parent * - child1-1 * - child1-2 * - subParent * - child2-1 * - child2-2 * include parents * </pre> */ @Test void nestedProjects() { MavenProject parent = createMavenProject("parent"); createMavenProject("child1-1", parent); createMavenProject("child1-2", parent); MavenProject subParent = createMavenProject("subParent", parent); createMavenProject("child2-1", subParent); createMavenProject("child2-2", subParent); AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, true); graphFactory.createGraph(parent); assertThat(this.graphBuilder, hasNodesAndEdges( new String[]{ "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\"", "\"groupId:subParent:jar:version:compile\"", "\"groupId:child2-1:jar:version:compile\"", "\"groupId:child2-2:jar:version:compile\""}, new String[]{ "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:subParent:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-1:jar:version:compile\"[style=dotted]", "\"groupId:subParent:jar:version:compile\" -> \"groupId:child2-2:jar:version:compile\"[style=dotted]"})); } /** * . * <pre> * parentParent (not part of graph) * - parent * - child * </pre> */ @Test void stopAtParent() { MavenProject parentParent = createMavenProject("parentParent"); MavenProject parent = createMavenProject("parent", parentParent); createMavenProject("child", parent); AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false); graphFactory.createGraph(parent); assertThat(this.graphBuilder, hasNodesAndEdges( new String[]{ "\"groupId:parent:jar:version:compile\"", "\"groupId:child:jar:version:compile\""}, new String[]{ "\"groupId:parent:jar:version:compile\" -> \"groupId:child1:jar:version:compile\"[style=dotted]"})); } /** * . * <pre> * parent * - child1-1 * - child1-2 * - subParent * - child2-1 * - child2-2 * subParent is excluded * </pre> */ @Test void filteredParent() { MavenProject parent = createMavenProject("parent"); createMavenProject("child1-1", parent); createMavenProject("child1-2", parent); MavenProject subParent = createMavenProject("subParent", parent); createMavenProject("child2-1", subParent); createMavenProject("child2-2", subParent); AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false); when(this.globalFilter.include(subParent.getArtifact())).thenReturn(false); graphFactory.createGraph(parent); assertThat(this.graphBuilder, hasNodesAndEdges( new String[]{ "\"groupId:parent:jar:version:compile\"", "\"groupId:child1-1:jar:version:compile\"", "\"groupId:child1-2:jar:version:compile\""}, new String[]{ "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-1:jar:version:compile\"[style=dotted]", "\"groupId:parent:jar:version:compile\" -> \"groupId:child1-2:jar:version:compile\"[style=dotted]"})); } /** * . * <pre> * parent * - child1 * - child2 * child1 is excluded * </pre> */ @Test void excludedArtifact() throws Exception { MavenProject parent = createMavenProject("parent"); MavenProject child1 = createMavenProject("child1", parent); createMavenProject("child2", parent); AggregatingGraphFactory graphFactory = new AggregatingGraphFactory(this.adapter, parent::getCollectedProjects, this.globalFilter, this.graphBuilder, true, false); when(this.globalFilter.include(child1.getArtifact())).thenReturn(false); graphFactory.createGraph(parent); // graph builder must not be invoked for child1 verify(this.dependenciesResolver, never()).resolve(argThat(projectName("child1"))); // module tree must not contain child1 assertThat(this.graphBuilder, hasNodesAndEdges( new String[]{ "\"groupId:parent:jar:version:compile\"", "\"groupId:child2:jar:version:compile\""}, new String[]{ "\"groupId:parent:jar:version:compile\" -> \"groupId:child2:jar:version:compile\"[style=dotted]"})); } private MavenProject createMavenProject(String artifactId) { MavenProject project = new MavenProject(); project.setArtifactId(artifactId); // Make sure that we can modify the list later. project.setCollectedProjects(new ArrayList<>()); DefaultArtifact artifact = new DefaultArtifact("groupId", artifactId, "version", "compile", "jar", "", null); project.setArtifact(artifact); RepositorySystemSession repositorySession = mock(RepositorySystemSession.class); ProjectBuildingRequest projectBuildingRequest = mock(ProjectBuildingRequest.class); when(projectBuildingRequest.getRepositorySession()).thenReturn(repositorySession); //noinspection deprecation project.setProjectBuildingRequest(projectBuildingRequest); return project; } private MavenProject createMavenProject(String artifactId, MavenProject parent) { MavenProject project = createMavenProject(artifactId); project.setParent(parent); parent.getModules().add(artifactId); MavenProject currentParent = parent; while (currentParent != null) { currentParent.getCollectedProjects().add(project); currentParent = currentParent.getParent(); } return project; } private static ArgumentMatcher<DependencyResolutionRequest> projectName(String projectName) { return new ProjectNameMatcher(projectName); } private static class ProjectNameMatcher implements ArgumentMatcher<DependencyResolutionRequest> { private final String expectedProjectName; ProjectNameMatcher(String projectName) { this.expectedProjectName = projectName; } @Override public boolean matches(DependencyResolutionRequest argument) { return argument.getMavenProject().getName().equals(this.expectedProjectName); } } }