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.eclipse.aether.util.graph.transformer;
20  
21  import java.util.ArrayList;
22  import java.util.Arrays;
23  import java.util.List;
24  
25  import org.eclipse.aether.DefaultRepositorySystemSession;
26  import org.eclipse.aether.RepositoryException;
27  import org.eclipse.aether.artifact.DefaultArtifact;
28  import org.eclipse.aether.graph.DefaultDependencyNode;
29  import org.eclipse.aether.graph.Dependency;
30  import org.eclipse.aether.graph.DependencyNode;
31  import org.eclipse.aether.internal.test.util.TestUtils;
32  import org.eclipse.aether.internal.test.util.TestVersion;
33  import org.eclipse.aether.internal.test.util.TestVersionConstraint;
34  import org.eclipse.aether.util.graph.visitor.DependencyGraphDumper;
35  import org.junit.jupiter.api.Test;
36  
37  import static org.junit.jupiter.api.Assertions.*;
38  
39  public class ConflictResolverTest {
40      @Test
41      void noTransformationRequired() throws RepositoryException {
42          ConflictResolver resolver = makeDefaultResolver();
43  
44          // Foo -> Bar
45          DependencyNode fooNode = makeDependencyNode("group-id", "foo", "1.0");
46          DependencyNode barNode = makeDependencyNode("group-id", "bar", "1.0");
47          fooNode.setChildren(mutableList(barNode));
48  
49          DependencyNode transformedNode =
50                  resolver.transformGraph(fooNode, TestUtils.newTransformationContext(TestUtils.newSession()));
51  
52          assertSame(fooNode, transformedNode);
53          assertEquals(1, transformedNode.getChildren().size());
54          assertSame(barNode, transformedNode.getChildren().get(0));
55      }
56  
57      @Test
58      void versionClash() throws RepositoryException {
59          ConflictResolver resolver = makeDefaultResolver();
60  
61          // Foo -> Bar -> Baz 2.0
62          //  |---> Baz 1.0
63          DependencyNode fooNode = makeDependencyNode("some-group", "foo", "1.0");
64          DependencyNode barNode = makeDependencyNode("some-group", "bar", "1.0");
65          DependencyNode baz1Node = makeDependencyNode("some-group", "baz", "1.0");
66          DependencyNode baz2Node = makeDependencyNode("some-group", "baz", "2.0");
67          fooNode.setChildren(mutableList(barNode, baz1Node));
68          barNode.setChildren(mutableList(baz2Node));
69  
70          DependencyNode transformedNode =
71                  resolver.transformGraph(fooNode, TestUtils.newTransformationContext(TestUtils.newSession()));
72  
73          assertSame(fooNode, transformedNode);
74          assertEquals(2, fooNode.getChildren().size());
75          assertSame(barNode, fooNode.getChildren().get(0));
76          assertTrue(barNode.getChildren().isEmpty());
77          assertSame(baz1Node, fooNode.getChildren().get(1));
78      }
79  
80      @Test
81      void versionClashForkedStandardVerbose() throws RepositoryException {
82  
83          // root -> impl1 -> api:1
84          //  |----> impl2 -> api:2
85          DependencyNode root = makeDependencyNode("some-group", "root", "1.0");
86          DependencyNode impl1 = makeDependencyNode("some-group", "impl1", "1.0");
87          DependencyNode impl2 = makeDependencyNode("some-group", "impl2", "1.0");
88          DependencyNode api1 = makeDependencyNode("some-group", "api", "1.1");
89          DependencyNode api2 = makeDependencyNode("some-group", "api", "1.0");
90  
91          root.setChildren(mutableList(impl1, impl2));
92          impl1.setChildren(mutableList(api1));
93          impl2.setChildren(mutableList(api2));
94  
95          DependencyNode transformedNode = versionRangeClash(root, ConflictResolver.Verbosity.STANDARD);
96  
97          assertSame(root, transformedNode);
98          assertEquals(2, root.getChildren().size());
99          assertSame(impl1, root.getChildren().get(0));
100         assertSame(impl2, root.getChildren().get(1));
101         assertEquals(1, impl1.getChildren().size());
102         assertSame(api1, impl1.getChildren().get(0));
103         assertEquals(1, impl2.getChildren().size());
104         assertConflictedButSameAsOriginal(api2, impl2.getChildren().get(0));
105     }
106 
107     @Test
108     void versionRangeClashAscOrder() throws RepositoryException {
109         //  A -> B -> C[1..2]
110         //  \--> C[1..2]
111         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
112         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
113         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
114         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
115         a.setChildren(mutableList(b, c1, c2));
116         b.setChildren(mutableList(c1, c2));
117 
118         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.NONE);
119 
120         assertSame(a, ta);
121         assertEquals(2, a.getChildren().size());
122         assertSame(b, a.getChildren().get(0));
123         assertSame(c2, a.getChildren().get(1));
124         assertEquals(0, b.getChildren().size());
125     }
126 
127     @Test
128     void versionRangeClashAscOrderStandardVerbose() throws RepositoryException {
129         //  A -> B -> C[1..2]
130         //  \--> C[1..2]
131         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
132         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
133         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
134         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
135         a.setChildren(mutableList(b, c1, c2));
136         b.setChildren(mutableList(c1, c2));
137 
138         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.STANDARD);
139 
140         assertSame(a, ta);
141         assertEquals(2, a.getChildren().size());
142         assertSame(b, a.getChildren().get(0));
143         assertSame(c2, a.getChildren().get(1));
144         assertEquals(1, b.getChildren().size());
145         assertConflictedButSameAsOriginal(c2, b.getChildren().get(0));
146     }
147 
148     @Test
149     void versionRangeClashAscOrderFullVerbose() throws RepositoryException {
150         //  A -> B -> C[1..2]
151         //  \--> C[1..2]
152         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
153         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
154         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
155         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
156         a.setChildren(mutableList(b, c1, c2));
157         b.setChildren(mutableList(c1, c2));
158 
159         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.FULL);
160 
161         assertSame(a, ta);
162         assertEquals(3, a.getChildren().size());
163         assertSame(b, a.getChildren().get(0));
164         assertConflictedButSameAsOriginal(c1, a.getChildren().get(1));
165         assertSame(c2, a.getChildren().get(2));
166         assertEquals(2, b.getChildren().size());
167         assertConflictedButSameAsOriginal(c1, b.getChildren().get(0));
168         assertConflictedButSameAsOriginal(c2, b.getChildren().get(1));
169     }
170 
171     @Test
172     void versionRangeClashDescOrder() throws RepositoryException {
173         //  A -> B -> C[1..2]
174         //  \--> C[1..2]
175         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
176         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
177         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
178         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
179         a.setChildren(mutableList(b, c2, c1));
180         b.setChildren(mutableList(c2, c1));
181 
182         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.NONE);
183 
184         assertSame(a, ta);
185         assertEquals(2, a.getChildren().size());
186         assertSame(b, a.getChildren().get(0));
187         assertSame(c2, a.getChildren().get(1));
188         assertEquals(0, b.getChildren().size());
189     }
190 
191     @Test
192     void versionRangeClashDescOrderStandardVerbose() throws RepositoryException {
193         //  A -> B -> C[1..2]
194         //  \--> C[1..2]
195         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
196         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
197         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
198         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
199         a.setChildren(mutableList(b, c2, c1));
200         b.setChildren(mutableList(c2, c1));
201 
202         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.STANDARD);
203 
204         assertSame(a, ta);
205         assertEquals(2, a.getChildren().size());
206         assertSame(b, a.getChildren().get(0));
207         assertSame(c2, a.getChildren().get(1));
208         assertEquals(1, b.getChildren().size());
209         assertConflictedButSameAsOriginal(c2, b.getChildren().get(0));
210     }
211 
212     @Test
213     void versionRangeClashDescOrderFullVerbose() throws RepositoryException {
214         //  A -> B -> C[1..2]
215         //  \--> C[1..2]
216         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
217         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
218         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
219         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
220         a.setChildren(mutableList(b, c2, c1));
221         b.setChildren(mutableList(c2, c1));
222 
223         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.FULL);
224 
225         assertSame(a, ta);
226         assertEquals(3, a.getChildren().size());
227         assertSame(b, a.getChildren().get(0));
228         assertSame(c2, a.getChildren().get(1));
229         assertConflictedButSameAsOriginal(c1, a.getChildren().get(2));
230         assertEquals(2, b.getChildren().size());
231         assertConflictedButSameAsOriginal(c2, b.getChildren().get(0));
232         assertConflictedButSameAsOriginal(c1, b.getChildren().get(1));
233     }
234 
235     @Test
236     void versionRangeClashMixedOrder() throws RepositoryException {
237         //  A -> B -> C[1..2]
238         //  \--> C[1..2]
239         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
240         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
241         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
242         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
243         a.setChildren(mutableList(b, c2, c1));
244         b.setChildren(mutableList(c1, c2));
245 
246         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.NONE);
247 
248         assertSame(a, ta);
249         assertEquals(2, a.getChildren().size());
250         assertSame(b, a.getChildren().get(0));
251         assertSame(c2, a.getChildren().get(1));
252         assertEquals(0, b.getChildren().size());
253     }
254 
255     @Test
256     void versionRangeClashMixedOrderStandardVerbose() throws RepositoryException {
257         //  A -> B -> C[1..2]
258         //  \--> C[1..2]
259         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
260         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
261         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
262         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
263         a.setChildren(mutableList(b, c2, c1));
264         b.setChildren(mutableList(c1, c2));
265 
266         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.STANDARD);
267 
268         assertSame(a, ta);
269         assertEquals(2, a.getChildren().size());
270         assertSame(b, a.getChildren().get(0));
271         assertSame(c2, a.getChildren().get(1));
272         assertEquals(1, b.getChildren().size());
273         assertConflictedButSameAsOriginal(c2, b.getChildren().get(0));
274     }
275 
276     @Test
277     void versionRangeClashMixedOrderStandardVerboseLeavesOne() throws RepositoryException {
278         // This is a bit different then others, is related to MRESOLVER-357 and makes sure that
279         // ConflictResolver fulfils the promise of "leaving 1 loser"
280         //
281         //  A -> B -> C[1..2]
282         //  |    \ -> D1
283         //  |--> C[1..2]
284         //  \--> D2
285         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
286         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
287         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
288         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
289         DependencyNode d1 = makeDependencyNode("some-group", "d", "1.0");
290         DependencyNode d2 = makeDependencyNode("some-group", "d", "2.0");
291         a.setChildren(mutableList(b, c2, c1, d2));
292         b.setChildren(mutableList(c1, c2, d1));
293 
294         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.STANDARD);
295 
296         assertSame(a, ta);
297         assertEquals(3, a.getChildren().size());
298         assertSame(b, a.getChildren().get(0));
299         assertSame(c2, a.getChildren().get(1));
300         assertSame(d2, a.getChildren().get(2));
301         assertEquals(2, b.getChildren().size());
302         assertConflictedButSameAsOriginal(c2, b.getChildren().get(0));
303         assertConflictedButSameAsOriginal(d1, b.getChildren().get(1));
304     }
305 
306     @Test
307     void versionRangeClashMixedOrderFullVerbose() throws RepositoryException {
308         //  A -> B -> C[1..2]
309         //  \--> C[1..2]
310         DependencyNode a = makeDependencyNode("some-group", "a", "1.0");
311         DependencyNode b = makeDependencyNode("some-group", "b", "1.0");
312         DependencyNode c1 = makeDependencyNode("some-group", "c", "1.0");
313         DependencyNode c2 = makeDependencyNode("some-group", "c", "2.0");
314         a.setChildren(mutableList(b, c2, c1));
315         b.setChildren(mutableList(c1, c2));
316 
317         DependencyNode ta = versionRangeClash(a, ConflictResolver.Verbosity.FULL);
318 
319         assertSame(a, ta);
320         assertEquals(3, a.getChildren().size());
321         assertSame(b, a.getChildren().get(0));
322         assertSame(c2, a.getChildren().get(1));
323         assertConflictedButSameAsOriginal(c1, a.getChildren().get(2));
324         assertEquals(2, b.getChildren().size());
325         assertConflictedButSameAsOriginal(c1, b.getChildren().get(0));
326         assertConflictedButSameAsOriginal(c2, b.getChildren().get(1));
327     }
328 
329     /**
330      * Conflict resolver in case of conflict replaces {@link DependencyNode} instances with copies to keep them
331      * stateful on different levels of graph and records conflict data. This method assert that two nodes do represent
332      * same dependency (same GAV, scope, optionality), but that original is not conflicted while current is.
333      */
334     private void assertConflictedButSameAsOriginal(DependencyNode original, DependencyNode current) {
335         assertNotSame(original, current);
336         assertEquals(
337                 original.getDependency().getArtifact(), current.getDependency().getArtifact());
338         assertEquals(
339                 original.getDependency().getScope(), current.getDependency().getScope());
340         assertEquals(
341                 original.getDependency().getOptional(), current.getDependency().getOptional());
342         assertNull(original.getData().get(ConflictResolver.NODE_DATA_WINNER));
343         assertNotNull(current.getData().get(ConflictResolver.NODE_DATA_WINNER));
344     }
345 
346     private static final DependencyGraphDumper DUMPER_SOUT = new DependencyGraphDumper(System.out::println);
347 
348     /**
349      * Performs a verbose conflict resolution on passed in root.
350      */
351     private DependencyNode versionRangeClash(DependencyNode root, ConflictResolver.Verbosity verbosity)
352             throws RepositoryException {
353         ConflictResolver resolver = makeDefaultResolver();
354 
355         System.out.println();
356         System.out.println("Input node:");
357         root.accept(DUMPER_SOUT);
358 
359         DefaultRepositorySystemSession session = TestUtils.newSession();
360         session.setConfigProperty(ConflictResolver.CONFIG_PROP_VERBOSE, verbosity);
361         DependencyNode transformedRoot = resolver.transformGraph(root, TestUtils.newTransformationContext(session));
362 
363         System.out.println();
364         System.out.println("Transformed node:");
365         transformedRoot.accept(DUMPER_SOUT);
366 
367         return transformedRoot;
368     }
369 
370     @Test
371     void derivedScopeChange() throws RepositoryException {
372         ConflictResolver resolver = makeDefaultResolver();
373 
374         // Foo -> Bar (test) -> Jaz
375         //  |---> Baz -> Jaz
376         DependencyNode fooNode = makeDependencyNode("some-group", "foo", "1.0");
377         DependencyNode barNode = makeDependencyNode("some-group", "bar", "1.0", "test");
378         DependencyNode bazNode = makeDependencyNode("some-group", "baz", "1.0");
379         DependencyNode jazNode = makeDependencyNode("some-group", "jaz", "1.0");
380         fooNode.setChildren(mutableList(barNode, bazNode));
381 
382         List<DependencyNode> jazList = mutableList(jazNode);
383         barNode.setChildren(jazList);
384         bazNode.setChildren(jazList);
385 
386         DependencyNode transformedNode =
387                 resolver.transformGraph(fooNode, TestUtils.newTransformationContext(TestUtils.newSession()));
388 
389         assertSame(fooNode, transformedNode);
390         assertEquals(2, fooNode.getChildren().size());
391         assertSame(barNode, fooNode.getChildren().get(0));
392         assertEquals(1, barNode.getChildren().size());
393         assertSame(jazNode, barNode.getChildren().get(0));
394         assertSame(bazNode, fooNode.getChildren().get(1));
395         assertEquals(1, barNode.getChildren().size());
396         assertSame(jazNode, barNode.getChildren().get(0));
397     }
398 
399     @Test
400     void derivedOptionalStatusChange() throws RepositoryException {
401         ConflictResolver resolver = makeDefaultResolver();
402 
403         // Foo -> Bar (optional) -> Jaz
404         //  |---> Baz -> Jaz
405         DependencyNode fooNode = makeDependencyNode("some-group", "foo", "1.0");
406         DependencyNode barNode = makeDependencyNode("some-group", "bar", "1.0");
407         barNode.setOptional(true);
408         DependencyNode bazNode = makeDependencyNode("some-group", "baz", "1.0");
409         DependencyNode jazNode = makeDependencyNode("some-group", "jaz", "1.0");
410         fooNode.setChildren(mutableList(barNode, bazNode));
411 
412         List<DependencyNode> jazList = mutableList(jazNode);
413         barNode.setChildren(jazList);
414         bazNode.setChildren(jazList);
415 
416         DependencyNode transformedNode =
417                 resolver.transformGraph(fooNode, TestUtils.newTransformationContext(TestUtils.newSession()));
418 
419         assertSame(fooNode, transformedNode);
420         assertEquals(2, fooNode.getChildren().size());
421         assertSame(barNode, fooNode.getChildren().get(0));
422         assertEquals(1, barNode.getChildren().size());
423         assertSame(jazNode, barNode.getChildren().get(0));
424         assertSame(bazNode, fooNode.getChildren().get(1));
425         assertEquals(1, barNode.getChildren().size());
426         assertSame(jazNode, barNode.getChildren().get(0));
427     }
428 
429     private static ConflictResolver makeDefaultResolver() {
430         return new ConflictResolver(
431                 new NearestVersionSelector(),
432                 new JavaScopeSelector(),
433                 new SimpleOptionalitySelector(),
434                 new JavaScopeDeriver());
435     }
436 
437     private static DependencyNode makeDependencyNode(String groupId, String artifactId, String version) {
438         return makeDependencyNode(groupId, artifactId, version, "compile");
439     }
440 
441     private static DependencyNode makeDependencyNode(String groupId, String artifactId, String version, String scope) {
442         DefaultDependencyNode node = new DefaultDependencyNode(
443                 new Dependency(new DefaultArtifact(groupId + ':' + artifactId + ':' + version), scope));
444         node.setVersion(new TestVersion(version));
445         node.setVersionConstraint(new TestVersionConstraint(node.getVersion()));
446         return node;
447     }
448 
449     private static List<DependencyNode> mutableList(DependencyNode... nodes) {
450         return new ArrayList<>(Arrays.asList(nodes));
451     }
452 }