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.plugins.dependency.tree;
20  
21  import javax.inject.Inject;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.OutputStreamWriter;
26  import java.io.StringReader;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.nio.file.Paths;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.Comparator;
33  import java.util.List;
34  import java.util.Set;
35  import java.util.stream.Collectors;
36  
37  import jakarta.json.Json;
38  import jakarta.json.JsonArray;
39  import jakarta.json.JsonObject;
40  import jakarta.json.JsonReader;
41  import org.apache.maven.api.plugin.testing.InjectMojo;
42  import org.apache.maven.api.plugin.testing.MojoParameter;
43  import org.apache.maven.api.plugin.testing.MojoTest;
44  import org.apache.maven.artifact.Artifact;
45  import org.apache.maven.execution.MavenSession;
46  import org.apache.maven.plugins.dependency.testUtils.DependencyArtifactStubFactory;
47  import org.apache.maven.project.DefaultProjectBuildingRequest;
48  import org.apache.maven.project.MavenProject;
49  import org.apache.maven.shared.dependency.graph.DependencyNode;
50  import org.apache.maven.shared.dependency.graph.internal.DefaultDependencyNode;
51  import org.junit.jupiter.api.BeforeEach;
52  import org.junit.jupiter.api.Test;
53  import org.junit.jupiter.api.io.TempDir;
54  
55  import static org.apache.maven.api.plugin.testing.MojoExtension.setVariableValueToObject;
56  import static org.junit.jupiter.api.Assertions.assertEquals;
57  import static org.junit.jupiter.api.Assertions.assertNotNull;
58  import static org.junit.jupiter.api.Assertions.assertTrue;
59  import static org.mockito.Mockito.doAnswer;
60  import static org.mockito.Mockito.spy;
61  import static org.mockito.Mockito.when;
62  
63  /**
64   * Tests <code>TreeMojo</code>.
65   *
66   * @author <a href="mailto:markhobson@gmail.com">Mark Hobson</a>
67   * @version $Id$
68   * @since 2.0
69   */
70  @MojoTest(realRepositorySession = true)
71  class TestTreeMojo {
72  
73      @TempDir
74      private File tempDir;
75  
76      @Inject
77      private MavenSession session;
78  
79      private DependencyArtifactStubFactory stubFactory;
80  
81      @BeforeEach
82      void setUp() {
83          stubFactory = new DependencyArtifactStubFactory(tempDir, true, false);
84          session.getRequest().setLocalRepositoryPath(new File(tempDir, "localTestRepo"));
85  
86          DefaultProjectBuildingRequest pbr = spy(new DefaultProjectBuildingRequest());
87          doAnswer(__ -> session.getRepositorySession()).when(pbr).getRepositorySession();
88          when(session.getProjectBuildingRequest()).thenReturn(pbr);
89      }
90  
91      // tests ------------------------------------------------------------------
92  
93      /**
94       * Tests the proper discovery and configuration of the mojo.
95       *
96       * @throws Exception in case of an error.
97       */
98      @Test
99      @InjectMojo(goal = "tree")
100     void testTreeTestEnvironment(TreeMojo mojo) throws Exception {
101         assertNotNull(mojo);
102         assertNotNull(mojo.getProject());
103         MavenProject project = mojo.getProject();
104         project.setArtifact(this.stubFactory.createArtifact("testGroupId", "project", "1.0"));
105 
106         Set<Artifact> artifacts = this.stubFactory.getScopedArtifacts();
107         Set<Artifact> directArtifacts = this.stubFactory.getReleaseAndSnapshotArtifacts();
108         artifacts.addAll(directArtifacts);
109 
110         project.setArtifacts(artifacts);
111         project.setDependencyArtifacts(directArtifacts);
112 
113         mojo.execute();
114 
115         DependencyNode rootNode = mojo.getDependencyGraph();
116         assertNodeEquals("testGroupId:project:jar:1.0:compile", rootNode);
117         assertEquals(2, rootNode.getChildren().size());
118 
119         List<String> actualNodes = Arrays.asList(
120                 createArtifactCoordinateString(rootNode.getChildren().get(0)),
121                 createArtifactCoordinateString(rootNode.getChildren().get(1)));
122         List<String> expectedNodes =
123                 Arrays.asList("testGroupId:release:jar:1.0:compile", "testGroupId:snapshot:jar:2.0-SNAPSHOT:compile");
124 
125         assertTrue(expectedNodes.containsAll(actualNodes));
126     }
127 
128     /**
129      * Test the DOT format serialization
130      *
131      * @throws Exception in case of an error.
132      */
133     @Test
134     @InjectMojo(goal = "tree")
135     @MojoParameter(name = "outputType", value = "dot")
136     void testTreeDotSerializing(TreeMojo mojo) throws Exception {
137         List<String> contents = runTreeMojo(mojo);
138         assertTrue(findString(contents, "digraph \"testGroupId:project:jar:1.0:compile\" {"));
139         assertTrue(findString(
140                 contents,
141                 "\"testGroupId:project:jar:1.0:compile\" -> \"testGroupId:snapshot:jar:2.0-SNAPSHOT:compile\""));
142         assertTrue(findString(
143                 contents, "\"testGroupId:project:jar:1.0:compile\" -> \"testGroupId:release:jar:1.0:compile\""));
144     }
145 
146     /**
147      * Test the GraphML format serialization
148      *
149      * @throws Exception in case of an error.
150      */
151     @Test
152     @InjectMojo(goal = "tree")
153     @MojoParameter(name = "outputType", value = "graphml")
154     void testTreeGraphMLSerializing(TreeMojo mojo) throws Exception {
155         List<String> contents = runTreeMojo(mojo);
156 
157         assertTrue(findString(contents, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"));
158         assertTrue(findString(contents, "<y:NodeLabel>testGroupId:project:jar:1.0:compile</y:NodeLabel>"));
159         assertTrue(findString(contents, "<y:NodeLabel>testGroupId:snapshot:jar:2.0-SNAPSHOT:compile</y:NodeLabel>"));
160         assertTrue(findString(contents, "<y:NodeLabel>testGroupId:release:jar:1.0:compile</y:NodeLabel>"));
161         assertTrue(findString(contents, "<key for=\"node\" id=\"d0\" yfiles.type=\"nodegraphics\"/>"));
162         assertTrue(findString(contents, "<key for=\"edge\" id=\"d1\" yfiles.type=\"edgegraphics\"/>"));
163     }
164 
165     /**
166      * Test the TGF format serialization
167      *
168      * @throws Exception in case of an error.
169      */
170     @Test
171     @InjectMojo(goal = "tree")
172     @MojoParameter(name = "outputType", value = "tgf")
173     void testTreeTGFSerializing(TreeMojo mojo) throws Exception {
174         List<String> contents = runTreeMojo(mojo);
175         assertTrue(findString(contents, "testGroupId:project:jar:1.0:compile"));
176         assertTrue(findString(contents, "testGroupId:snapshot:jar:2.0-SNAPSHOT:compile"));
177         assertTrue(findString(contents, "testGroupId:release:jar:1.0:compile"));
178     }
179 
180     /**
181      * Test the JSON format serialization on DependencyNodes with circular dependence
182      */
183     @Test
184     void testTreeJsonCircularDependency() throws IOException {
185         File outputFile = new File(tempDir, "tree1.json");
186         Files.createDirectories(outputFile.getParentFile().toPath());
187         outputFile.createNewFile();
188 
189         Artifact artifact1 = this.stubFactory.createArtifact("testGroupId", "project1", "1.0");
190         Artifact artifact2 = this.stubFactory.createArtifact("testGroupId", "project2", "1.0");
191         DefaultDependencyNode node1 = new DefaultDependencyNode(artifact1);
192         DefaultDependencyNode node2 = new DefaultDependencyNode(artifact2);
193 
194         node1.setChildren(new ArrayList<>());
195         node2.setChildren(new ArrayList<>());
196 
197         node1.getChildren().add(node2);
198         node2.getChildren().add(node1);
199 
200         try (OutputStreamWriter outputStreamWriter =
201                 new OutputStreamWriter(Files.newOutputStream(outputFile.toPath()))) {
202             JsonDependencyNodeVisitor jsonDependencyNodeVisitor = new JsonDependencyNodeVisitor(outputStreamWriter);
203 
204             jsonDependencyNodeVisitor.visit(node1);
205         }
206     }
207 
208     /*
209      * Test parsing of Json output and verify all key-value pairs
210      */
211     @Test
212     @InjectMojo(goal = "tree")
213     @MojoParameter(name = "outputType", value = "json")
214     void testTreeJsonParsing(TreeMojo mojo) throws Exception {
215         List<String> contents = runTreeMojo(mojo);
216 
217         System.setProperty("jakarta.json.provider", "org.glassfish.json.JsonProviderImpl");
218         try (JsonReader reader = Json.createReader(new StringReader(String.join(System.lineSeparator(), contents)))) {
219             JsonObject root = reader.readObject();
220 
221             assertEquals("testGroupId", root.getString("groupId"));
222             assertEquals("project", root.getString("artifactId"));
223             assertEquals("1.0", root.getString("version"));
224             assertEquals("jar", root.getString("type"));
225             assertEquals("compile", root.getString("scope"));
226             assertEquals("", root.getString("classifier"));
227             assertEquals("false", root.getString("optional"));
228 
229             JsonArray children = root.getJsonArray("children");
230             assertEquals(2, children.size());
231 
232             List<JsonObject> sortedChildren = children.stream()
233                     .map(JsonObject.class::cast)
234                     .sorted(Comparator.comparing(child -> child.getString("artifactId")))
235                     .collect(Collectors.toList());
236 
237             // Now that children are sorted, we can assert their values in a fixed order
238             JsonObject child0 = sortedChildren.get(0);
239             assertEquals("testGroupId", child0.getString("groupId"));
240             assertEquals("release", child0.getString("artifactId"));
241             assertEquals("1.0", child0.getString("version"));
242             assertEquals("jar", child0.getString("type"));
243             assertEquals("compile", child0.getString("scope"));
244             assertEquals("", child0.getString("classifier"));
245             assertEquals("false", child0.getString("optional"));
246 
247             JsonObject child1 = sortedChildren.get(1);
248             assertEquals("testGroupId", child1.getString("groupId"));
249             assertEquals("snapshot", child1.getString("artifactId"));
250             assertEquals("2.0-SNAPSHOT", child1.getString("version"));
251             assertEquals("jar", child1.getString("type"));
252             assertEquals("compile", child1.getString("scope"));
253             assertEquals("", child1.getString("classifier"));
254             assertEquals("false", child1.getString("optional"));
255         }
256     }
257 
258     /**
259      * Help finding content in the given list of string
260      *
261      * @throws Exception in case of an error.
262      * @return list of strings in the output file
263      */
264     private List<String> runTreeMojo(TreeMojo mojo) throws Exception {
265         Path outputFilePath = Paths.get(tempDir.getPath(), "outputFile.txt");
266         setVariableValueToObject(mojo, "outputEncoding", "UTF-8");
267         setVariableValueToObject(mojo, "outputFile", outputFilePath.toFile());
268 
269         assertNotNull(mojo);
270         assertNotNull(mojo.getProject());
271         MavenProject project = mojo.getProject();
272         project.setArtifact(this.stubFactory.createArtifact("testGroupId", "project", "1.0"));
273 
274         Set<Artifact> artifacts = this.stubFactory.getScopedArtifacts();
275         Set<Artifact> directArtifacts = this.stubFactory.getReleaseAndSnapshotArtifacts();
276         artifacts.addAll(directArtifacts);
277 
278         project.setArtifacts(artifacts);
279         project.setDependencyArtifacts(directArtifacts);
280 
281         mojo.execute();
282 
283         return Files.readAllLines(outputFilePath);
284     }
285 
286     /**
287      * Help finding content in the given list of string
288      *
289      * @param contents The contents.
290      * @param str The content which should be checked for.
291      */
292     private boolean findString(List<String> contents, String str) {
293         for (String line : contents) {
294             if (line.contains(str)) {
295                 // if match then return here
296                 return true;
297             }
298         }
299 
300         // in case no match for the whole list
301         return false;
302     }
303 
304     // private methods --------------------------------------------------------
305 
306     private void assertNodeEquals(String expectedNode, DependencyNode actualNode) {
307         String[] tokens = expectedNode.split(":");
308 
309         assertNodeEquals(tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], actualNode);
310     }
311 
312     private void assertNodeEquals(
313             String expectedGroupId,
314             String expectedArtifactId,
315             String expectedType,
316             String expectedVersion,
317             String expectedScope,
318             DependencyNode actualNode) {
319         Artifact actualArtifact = actualNode.getArtifact();
320 
321         assertEquals(expectedGroupId, actualArtifact.getGroupId(), "group id");
322         assertEquals(expectedArtifactId, actualArtifact.getArtifactId(), "artifact id");
323         assertEquals(expectedType, actualArtifact.getType(), "type");
324         assertEquals(expectedVersion, actualArtifact.getVersion(), "version");
325         assertEquals(expectedScope, actualArtifact.getScope(), "scope");
326     }
327 
328     private String createArtifactCoordinateString(DependencyNode actualNode) {
329         return actualNode.getArtifact().getGroupId() + ":"
330                 + actualNode.getArtifact().getArtifactId() + ":"
331                 + actualNode.getArtifact().getType() + ":"
332                 + actualNode.getArtifact().getVersion() + ":"
333                 + actualNode.getArtifact().getScope();
334     }
335 }