001package org.eclipse.aether.internal.impl.collect;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import static org.junit.Assert.*;
023
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.Collections;
029import java.util.HashMap;
030import java.util.Iterator;
031import java.util.LinkedList;
032import java.util.List;
033import java.util.Map;
034
035import org.eclipse.aether.DefaultRepositorySystemSession;
036import org.eclipse.aether.RepositorySystemSession;
037import org.eclipse.aether.artifact.Artifact;
038import org.eclipse.aether.artifact.ArtifactProperties;
039import org.eclipse.aether.artifact.DefaultArtifact;
040import org.eclipse.aether.collection.CollectRequest;
041import org.eclipse.aether.collection.CollectResult;
042import org.eclipse.aether.collection.DependencyCollectionContext;
043import org.eclipse.aether.collection.DependencyCollectionException;
044import org.eclipse.aether.collection.DependencyManagement;
045import org.eclipse.aether.collection.DependencyManager;
046import org.eclipse.aether.graph.Dependency;
047import org.eclipse.aether.graph.DependencyCycle;
048import org.eclipse.aether.graph.DependencyNode;
049import org.eclipse.aether.graph.Exclusion;
050import org.eclipse.aether.impl.ArtifactDescriptorReader;
051import org.eclipse.aether.internal.impl.IniArtifactDescriptorReader;
052import org.eclipse.aether.internal.impl.StubRemoteRepositoryManager;
053import org.eclipse.aether.internal.impl.StubVersionRangeResolver;
054import org.eclipse.aether.internal.test.util.DependencyGraphParser;
055import org.eclipse.aether.internal.test.util.TestUtils;
056import org.eclipse.aether.repository.RemoteRepository;
057import org.eclipse.aether.resolution.ArtifactDescriptorException;
058import org.eclipse.aether.resolution.ArtifactDescriptorRequest;
059import org.eclipse.aether.resolution.ArtifactDescriptorResult;
060import org.eclipse.aether.util.artifact.ArtifactIdUtils;
061import org.eclipse.aether.util.graph.manager.ClassicDependencyManager;
062import org.eclipse.aether.util.graph.manager.DependencyManagerUtils;
063import org.eclipse.aether.util.graph.version.HighestVersionFilter;
064import org.junit.Before;
065import org.junit.Test;
066
067/**
068 */
069public class DefaultDependencyCollectorTest
070{
071
072    private DefaultDependencyCollector collector;
073
074    private DefaultRepositorySystemSession session;
075
076    private DependencyGraphParser parser;
077
078    private RemoteRepository repository;
079
080    private IniArtifactDescriptorReader newReader( String prefix )
081    {
082        return new IniArtifactDescriptorReader( "artifact-descriptions/" + prefix );
083    }
084
085    private Dependency newDep( String coords )
086    {
087        return newDep( coords, "" );
088    }
089
090    private Dependency newDep( String coords, String scope )
091    {
092        return new Dependency( new DefaultArtifact( coords ), scope );
093    }
094
095    @Before
096    public void setup()
097        throws IOException
098    {
099        session = TestUtils.newSession();
100
101        collector = new DefaultDependencyCollector();
102        collector.setArtifactDescriptorReader( newReader( "" ) );
103        collector.setVersionRangeResolver( new StubVersionRangeResolver() );
104        collector.setRemoteRepositoryManager( new StubRemoteRepositoryManager() );
105
106        parser = new DependencyGraphParser( "artifact-descriptions/" );
107
108        repository = new RemoteRepository.Builder( "id", "default", "file:///" ).build();
109    }
110
111    private static void assertEqualSubtree( DependencyNode expected, DependencyNode actual )
112    {
113        assertEqualSubtree( expected, actual, new LinkedList<DependencyNode>() );
114    }
115
116    private static void assertEqualSubtree( DependencyNode expected, DependencyNode actual,
117                                            LinkedList<DependencyNode> parents )
118    {
119        assertEquals( "path: " + parents, expected.getDependency(), actual.getDependency() );
120
121        if ( actual.getDependency() != null )
122        {
123            Artifact artifact = actual.getDependency().getArtifact();
124            for ( DependencyNode parent : parents )
125            {
126                if ( parent.getDependency() != null && artifact.equals( parent.getDependency().getArtifact() ) )
127                {
128                    return;
129                }
130            }
131        }
132
133        parents.addLast( expected );
134
135        assertEquals( "path: " + parents + ", expected: " + expected.getChildren() + ", actual: "
136                          + actual.getChildren(), expected.getChildren().size(), actual.getChildren().size() );
137
138        Iterator<DependencyNode> iterator1 = expected.getChildren().iterator();
139        Iterator<DependencyNode> iterator2 = actual.getChildren().iterator();
140
141        while ( iterator1.hasNext() )
142        {
143            assertEqualSubtree( iterator1.next(), iterator2.next(), parents );
144        }
145
146        parents.removeLast();
147    }
148
149    private Dependency dep( DependencyNode root, int... coords )
150    {
151        return path( root, coords ).getDependency();
152    }
153
154    private DependencyNode path( DependencyNode root, int... coords )
155    {
156        try
157        {
158            DependencyNode node = root;
159            for ( int coord : coords )
160            {
161                node = node.getChildren().get( coord );
162            }
163
164            return node;
165        }
166        catch ( IndexOutOfBoundsException e )
167        {
168            throw new IllegalArgumentException( "illegal coordinates for child", e );
169        }
170        catch ( NullPointerException e )
171        {
172            throw new IllegalArgumentException( "illegal coordinates for child", e );
173        }
174    }
175
176    @Test
177    public void testSimpleCollection()
178        throws IOException, DependencyCollectionException
179    {
180        Dependency dependency = newDep( "gid:aid:ext:ver", "compile" );
181        CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) );
182        CollectResult result = collector.collectDependencies( session, request );
183
184        assertEquals( 0, result.getExceptions().size() );
185
186        DependencyNode root = result.getRoot();
187        Dependency newDependency = root.getDependency();
188
189        assertEquals( dependency, newDependency );
190        assertEquals( dependency.getArtifact(), newDependency.getArtifact() );
191
192        assertEquals( 1, root.getChildren().size() );
193
194        Dependency expect = newDep( "gid:aid2:ext:ver", "compile" );
195        assertEquals( expect, root.getChildren().get( 0 ).getDependency() );
196    }
197
198    @Test
199    public void testMissingDependencyDescription()
200        throws IOException
201    {
202        CollectRequest request =
203            new CollectRequest( newDep( "missing:description:ext:ver" ), Arrays.asList( repository ) );
204        try
205        {
206            collector.collectDependencies( session, request );
207            fail( "expected exception" );
208        }
209        catch ( DependencyCollectionException e )
210        {
211            CollectResult result = e.getResult();
212            assertSame( request, result.getRequest() );
213            assertNotNull( result.getExceptions() );
214            assertEquals( 1, result.getExceptions().size() );
215
216            assertTrue( result.getExceptions().get( 0 ) instanceof ArtifactDescriptorException );
217
218            assertEquals( request.getRoot(), result.getRoot().getDependency() );
219        }
220    }
221
222    @Test
223    public void testDuplicates()
224        throws IOException, DependencyCollectionException
225    {
226        Dependency dependency = newDep( "duplicate:transitive:ext:dependency" );
227        CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) );
228
229        CollectResult result = collector.collectDependencies( session, request );
230
231        assertEquals( 0, result.getExceptions().size() );
232
233        DependencyNode root = result.getRoot();
234        Dependency newDependency = root.getDependency();
235
236        assertEquals( dependency, newDependency );
237        assertEquals( dependency.getArtifact(), newDependency.getArtifact() );
238
239        assertEquals( 2, root.getChildren().size() );
240
241        Dependency dep = newDep( "gid:aid:ext:ver", "compile" );
242        assertEquals( dep, dep( root, 0 ) );
243
244        dep = newDep( "gid:aid2:ext:ver", "compile" );
245        assertEquals( dep, dep( root, 1 ) );
246        assertEquals( dep, dep( root, 0, 0 ) );
247        assertEquals( dep( root, 1 ), dep( root, 0, 0 ) );
248    }
249
250    @Test
251    public void testEqualSubtree()
252        throws IOException, DependencyCollectionException
253    {
254        DependencyNode root = parser.parseResource( "expectedSubtreeComparisonResult.txt" );
255        Dependency dependency = root.getDependency();
256        CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) );
257
258        CollectResult result = collector.collectDependencies( session, request );
259        assertEqualSubtree( root, result.getRoot() );
260    }
261
262    @Test
263    public void testCyclicDependencies()
264        throws Exception
265    {
266        DependencyNode root = parser.parseResource( "cycle.txt" );
267        CollectRequest request = new CollectRequest( root.getDependency(), Arrays.asList( repository ) );
268        CollectResult result = collector.collectDependencies( session, request );
269        assertEqualSubtree( root, result.getRoot() );
270    }
271
272    @Test
273    public void testCyclicDependenciesBig()
274        throws Exception
275    {
276        CollectRequest request = new CollectRequest( newDep( "1:2:pom:5.50-SNAPSHOT" ), Arrays.asList( repository ) );
277        collector.setArtifactDescriptorReader( newReader( "cycle-big/" ) );
278        CollectResult result = collector.collectDependencies( session, request );
279        assertNotNull( result.getRoot() );
280        // we only care about the performance here, this test must not hang or run out of mem
281    }
282
283    @Test
284    public void testCyclicProjects()
285        throws Exception
286    {
287        CollectRequest request = new CollectRequest( newDep( "test:a:2" ), Arrays.asList( repository ) );
288        collector.setArtifactDescriptorReader( newReader( "versionless-cycle/" ) );
289        CollectResult result = collector.collectDependencies( session, request );
290        DependencyNode root = result.getRoot();
291        DependencyNode a1 = path( root, 0, 0 );
292        assertEquals( "a", a1.getArtifact().getArtifactId() );
293        assertEquals( "1", a1.getArtifact().getVersion() );
294        for ( DependencyNode child : a1.getChildren() )
295        {
296            assertFalse( "1".equals( child.getArtifact().getVersion() ) );
297        }
298
299        assertEquals( 1, result.getCycles().size() );
300        DependencyCycle cycle = result.getCycles().get( 0 );
301        assertEquals( Arrays.asList(), cycle.getPrecedingDependencies() );
302        assertEquals( Arrays.asList( root.getDependency(), path( root, 0 ).getDependency(), a1.getDependency() ),
303                      cycle.getCyclicDependencies() );
304    }
305
306    @Test
307    public void testCyclicProjects_ConsiderLabelOfRootlessGraph()
308        throws Exception
309    {
310        Dependency dep = newDep( "gid:aid:ver", "compile" );
311        CollectRequest request =
312            new CollectRequest().addDependency( dep ).addRepository( repository ).setRootArtifact( dep.getArtifact() );
313        CollectResult result = collector.collectDependencies( session, request );
314        DependencyNode root = result.getRoot();
315        DependencyNode a1 = root.getChildren().get( 0 );
316        assertEquals( "aid", a1.getArtifact().getArtifactId() );
317        assertEquals( "ver", a1.getArtifact().getVersion() );
318        DependencyNode a2 = a1.getChildren().get( 0 );
319        assertEquals( "aid2", a2.getArtifact().getArtifactId() );
320        assertEquals( "ver", a2.getArtifact().getVersion() );
321
322        assertEquals( 1, result.getCycles().size() );
323        DependencyCycle cycle = result.getCycles().get( 0 );
324        assertEquals( Arrays.asList(), cycle.getPrecedingDependencies() );
325        assertEquals( Arrays.asList( new Dependency( dep.getArtifact(), null ), a1.getDependency() ),
326                      cycle.getCyclicDependencies() );
327    }
328
329    @Test
330    public void testPartialResultOnError()
331        throws IOException
332    {
333        DependencyNode root = parser.parseResource( "expectedPartialSubtreeOnError.txt" );
334
335        Dependency dependency = root.getDependency();
336        CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) );
337
338        CollectResult result;
339        try
340        {
341            result = collector.collectDependencies( session, request );
342            fail( "expected exception " );
343        }
344        catch ( DependencyCollectionException e )
345        {
346            result = e.getResult();
347
348            assertSame( request, result.getRequest() );
349            assertNotNull( result.getExceptions() );
350            assertEquals( 1, result.getExceptions().size() );
351
352            assertTrue( result.getExceptions().get( 0 ) instanceof ArtifactDescriptorException );
353
354            assertEqualSubtree( root, result.getRoot() );
355        }
356    }
357
358    @Test
359    public void testCollectMultipleDependencies()
360        throws IOException, DependencyCollectionException
361    {
362        Dependency root1 = newDep( "gid:aid:ext:ver", "compile" );
363        Dependency root2 = newDep( "gid:aid2:ext:ver", "compile" );
364        List<Dependency> dependencies = Arrays.asList( root1, root2 );
365        CollectRequest request = new CollectRequest( dependencies, null, Arrays.asList( repository ) );
366        CollectResult result = collector.collectDependencies( session, request );
367
368        assertEquals( 0, result.getExceptions().size() );
369        assertEquals( 2, result.getRoot().getChildren().size() );
370        assertEquals( root1, dep( result.getRoot(), 0 ) );
371
372        assertEquals( 1, path( result.getRoot(), 0 ).getChildren().size() );
373        assertEquals( root2, dep( result.getRoot(), 0, 0 ) );
374
375        assertEquals( 0, path( result.getRoot(), 1 ).getChildren().size() );
376        assertEquals( root2, dep( result.getRoot(), 1 ) );
377    }
378
379    @Test
380    public void testArtifactDescriptorResolutionNotRestrictedToRepoHostingSelectedVersion()
381        throws Exception
382    {
383        RemoteRepository repo2 = new RemoteRepository.Builder( "test", "default", "file:///" ).build();
384
385        final List<RemoteRepository> repos = new ArrayList<RemoteRepository>();
386
387        collector.setArtifactDescriptorReader( new ArtifactDescriptorReader()
388        {
389            public ArtifactDescriptorResult readArtifactDescriptor( RepositorySystemSession session,
390                                                                    ArtifactDescriptorRequest request )
391                throws ArtifactDescriptorException
392            {
393                repos.addAll( request.getRepositories() );
394                return new ArtifactDescriptorResult( request );
395            }
396        } );
397
398        List<Dependency> dependencies = Arrays.asList( newDep( "verrange:parent:jar:1[1,)", "compile" ) );
399        CollectRequest request = new CollectRequest( dependencies, null, Arrays.asList( repository, repo2 ) );
400        CollectResult result = collector.collectDependencies( session, request );
401
402        assertEquals( 0, result.getExceptions().size() );
403        assertEquals( 2, repos.size() );
404        assertEquals( "id", repos.get( 0 ).getId() );
405        assertEquals( "test", repos.get( 1 ).getId() );
406    }
407
408    @Test
409    public void testManagedVersionScope()
410        throws IOException, DependencyCollectionException
411    {
412        Dependency dependency = newDep( "managed:aid:ext:ver" );
413        CollectRequest request = new CollectRequest( dependency, Arrays.asList( repository ) );
414
415        session.setDependencyManager( new ClassicDependencyManager() );
416
417        CollectResult result = collector.collectDependencies( session, request );
418
419        assertEquals( 0, result.getExceptions().size() );
420
421        DependencyNode root = result.getRoot();
422
423        assertEquals( dependency, dep( root ) );
424        assertEquals( dependency.getArtifact(), dep( root ).getArtifact() );
425
426        assertEquals( 1, root.getChildren().size() );
427        Dependency expect = newDep( "gid:aid:ext:ver", "compile" );
428        assertEquals( expect, dep( root, 0 ) );
429
430        assertEquals( 1, path( root, 0 ).getChildren().size() );
431        expect = newDep( "gid:aid2:ext:managedVersion", "managedScope" );
432        assertEquals( expect, dep( root, 0, 0 ) );
433    }
434
435    @Test
436    public void testDependencyManagement()
437        throws IOException, DependencyCollectionException
438    {
439        collector.setArtifactDescriptorReader( newReader( "managed/" ) );
440
441        DependencyNode root = parser.parseResource( "expectedSubtreeComparisonResult.txt" );
442        TestDependencyManager depMgmt = new TestDependencyManager();
443        depMgmt.add( dep( root, 0 ), "managed", null, null );
444        depMgmt.add( dep( root, 0, 1 ), "managed", "managed", null );
445        depMgmt.add( dep( root, 1 ), null, null, "managed" );
446        session.setDependencyManager( depMgmt );
447
448        // collect result will differ from expectedSubtreeComparisonResult.txt
449        // set localPath -> no dependency traversal
450        CollectRequest request = new CollectRequest( dep( root ), Arrays.asList( repository ) );
451        CollectResult result = collector.collectDependencies( session, request );
452
453        DependencyNode node = result.getRoot();
454        assertEquals( "managed", dep( node, 0, 1 ).getArtifact().getVersion() );
455        assertEquals( "managed", dep( node, 0, 1 ).getScope() );
456
457        assertEquals( "managed", dep( node, 1 ).getArtifact().getProperty( ArtifactProperties.LOCAL_PATH, null ) );
458        assertEquals( "managed", dep( node, 0, 0 ).getArtifact().getProperty( ArtifactProperties.LOCAL_PATH, null ) );
459    }
460
461    @Test
462    public void testDependencyManagement_VerboseMode()
463        throws Exception
464    {
465        String depId = "gid:aid2:ext";
466        TestDependencyManager depMgmt = new TestDependencyManager();
467        depMgmt.version( depId, "managedVersion" );
468        depMgmt.scope( depId, "managedScope" );
469        depMgmt.optional( depId, Boolean.TRUE );
470        depMgmt.path( depId, "managedPath" );
471        depMgmt.exclusions( depId, new Exclusion( "gid", "aid", "*", "*" ) );
472        session.setDependencyManager( depMgmt );
473        session.setConfigProperty( DependencyManagerUtils.CONFIG_PROP_VERBOSE, Boolean.TRUE );
474
475        CollectRequest request = new CollectRequest().setRoot( newDep( "gid:aid:ver" ) );
476        CollectResult result = collector.collectDependencies( session, request );
477        DependencyNode node = result.getRoot().getChildren().get( 0 );
478        assertEquals( DependencyNode.MANAGED_VERSION | DependencyNode.MANAGED_SCOPE | DependencyNode.MANAGED_OPTIONAL
479            | DependencyNode.MANAGED_PROPERTIES | DependencyNode.MANAGED_EXCLUSIONS, node.getManagedBits() );
480        assertEquals( "ver", DependencyManagerUtils.getPremanagedVersion( node ) );
481        assertEquals( "compile", DependencyManagerUtils.getPremanagedScope( node ) );
482        assertEquals( Boolean.FALSE, DependencyManagerUtils.getPremanagedOptional( node ) );
483    }
484
485    @Test
486    public void testVersionFilter()
487        throws Exception
488    {
489        session.setVersionFilter( new HighestVersionFilter() );
490        CollectRequest request = new CollectRequest().setRoot( newDep( "gid:aid:1" ) );
491        CollectResult result = collector.collectDependencies( session, request );
492        assertEquals( 1, result.getRoot().getChildren().size() );
493    }
494
495    static class TestDependencyManager
496        implements DependencyManager
497    {
498
499        private Map<String, String> versions = new HashMap<String, String>();
500
501        private Map<String, String> scopes = new HashMap<String, String>();
502
503        private Map<String, Boolean> optionals = new HashMap<String, Boolean>();
504
505        private Map<String, String> paths = new HashMap<String, String>();
506
507        private Map<String, Collection<Exclusion>> exclusions = new HashMap<String, Collection<Exclusion>>();
508
509        public void add( Dependency d, String version, String scope, String localPath )
510        {
511            String id = toKey( d );
512            version( id, version );
513            scope( id, scope );
514            path( id, localPath );
515        }
516
517        public void version( String id, String version )
518        {
519            versions.put( id, version );
520        }
521
522        public void scope( String id, String scope )
523        {
524            scopes.put( id, scope );
525        }
526
527        public void optional( String id, Boolean optional )
528        {
529            optionals.put( id, optional );
530        }
531
532        public void path( String id, String path )
533        {
534            paths.put( id, path );
535        }
536
537        public void exclusions( String id, Exclusion... exclusions )
538        {
539            this.exclusions.put( id, exclusions != null ? Arrays.asList( exclusions ) : null );
540        }
541
542        public DependencyManagement manageDependency( Dependency d )
543        {
544            String id = toKey( d );
545            DependencyManagement mgmt = new DependencyManagement();
546            mgmt.setVersion( versions.get( id ) );
547            mgmt.setScope( scopes.get( id ) );
548            mgmt.setOptional( optionals.get( id ) );
549            String path = paths.get( id );
550            if ( path != null )
551            {
552                mgmt.setProperties( Collections.singletonMap( ArtifactProperties.LOCAL_PATH, path ) );
553            }
554            mgmt.setExclusions( exclusions.get( id ) );
555            return mgmt;
556        }
557
558        private String toKey( Dependency dependency )
559        {
560            return ArtifactIdUtils.toVersionlessId( dependency.getArtifact() );
561        }
562
563        public DependencyManager deriveChildManager( DependencyCollectionContext context )
564        {
565            return this;
566        }
567
568    }
569
570}