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