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