View Javadoc
1   package org.apache.maven.shared.release.phase;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static org.junit.Assert.assertFalse;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.nio.file.FileVisitResult;
27  import java.nio.file.Files;
28  import java.nio.file.LinkOption;
29  import java.nio.file.Path;
30  import java.nio.file.Paths;
31  import java.nio.file.SimpleFileVisitor;
32  import java.nio.file.StandardCopyOption;
33  import java.nio.file.attribute.BasicFileAttributes;
34  import java.util.ArrayList;
35  import java.util.Arrays;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Objects;
41  
42  import org.apache.commons.lang.SystemUtils;
43  import org.apache.maven.artifact.ArtifactUtils;
44  import org.apache.maven.artifact.factory.ArtifactFactory;
45  import org.apache.maven.artifact.repository.ArtifactRepository;
46  import org.apache.maven.artifact.repository.DefaultArtifactRepository;
47  import org.apache.maven.artifact.repository.MavenArtifactRepository;
48  import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
49  import org.apache.maven.artifact.repository.layout.DefaultRepositoryLayout;
50  import org.apache.maven.lifecycle.LifecycleExecutionException;
51  import org.apache.maven.model.Profile;
52  import org.apache.maven.model.Repository;
53  import org.apache.maven.project.DefaultProjectBuildingRequest;
54  import org.apache.maven.project.MavenProject;
55  import org.apache.maven.project.ProjectBuilder;
56  import org.apache.maven.project.ProjectBuildingRequest;
57  import org.apache.maven.project.ProjectBuildingRequest.RepositoryMerging;
58  import org.apache.maven.project.artifact.InvalidDependencyVersionException;
59  import org.apache.maven.project.ProjectBuildingResult;
60  import org.apache.maven.project.ProjectSorter;
61  import org.apache.maven.repository.internal.MavenRepositorySystemSession;
62  import org.apache.maven.shared.release.PlexusJUnit4TestCase;
63  import org.apache.maven.shared.release.config.ReleaseDescriptorBuilder;
64  import org.sonatype.aether.impl.internal.SimpleLocalRepositoryManager;
65  import org.sonatype.aether.repository.WorkspaceReader;
66  import org.sonatype.aether.repository.WorkspaceRepository;
67  import org.xmlunit.builder.DiffBuilder;
68  import org.xmlunit.diff.Comparison;
69  import org.xmlunit.diff.ComparisonResult;
70  import org.xmlunit.diff.ComparisonType;
71  import org.xmlunit.diff.DefaultNodeMatcher;
72  import org.xmlunit.diff.Diff;
73  import org.xmlunit.diff.DifferenceEvaluator;
74  import org.xmlunit.diff.ElementSelectors;
75  
76  /**
77   * Base class for some release tests.
78   *
79   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
80   */
81  public abstract class AbstractReleaseTestCase
82      extends PlexusJUnit4TestCase
83  {
84      protected ProjectBuilder projectBuilder;
85  
86      protected ArtifactRepository localRepository;
87      
88      private ArtifactFactory artifactFactory;
89  
90      protected ReleasePhase phase;
91  
92      @Override
93      public void setUp()
94          throws Exception
95      {
96          super.setUp();
97  
98          projectBuilder = lookup( ProjectBuilder.class );
99          artifactFactory = lookup( ArtifactFactory.class ); 
100 
101         ArtifactRepositoryLayout layout = lookup( ArtifactRepositoryLayout.class, "default" );
102         String localRepoPath = getTestFile( "target/local-repository" ).getAbsolutePath().replace( '\\', '/' );
103         localRepository = new MavenArtifactRepository( "local", "file://" + localRepoPath, layout, null, null );
104     }
105 
106     protected Path getWorkingDirectory( String workingDir )
107     {
108         return Paths.get( getBasedir(), "target/test-classes" ).resolve( Paths.get( "projects", workingDir ) ) ;
109     }
110 
111     protected List<MavenProject> createReactorProjects( String path, String subpath )
112         throws Exception
113     {
114         return createReactorProjects( path, path, subpath );
115     }
116     
117     protected ReleaseDescriptorBuilder createReleaseDescriptorBuilder( List<MavenProject> reactorProjects )
118     {
119         ReleaseDescriptorBuilder builder = new ReleaseDescriptorBuilder();
120         for ( MavenProject project : reactorProjects )
121         {
122             builder.putOriginalVersion( project.getGroupId() + ':' + project.getArtifactId(), project.getVersion() );
123         }
124         return builder;
125     }
126 
127     /**
128      *
129      * @param sourcePath sourceDirectory to copy from
130      * @param targetPath targetDirectory to copy to
131      * @param executionRoot sub directory of targetPath in case the root pom.xml is not used (e.g. flat projects)
132      * @return all Maven projects
133      * @throws Exception if any occurs
134      */
135     protected List<MavenProject> createReactorProjects( String sourcePath, String targetPath, String executionRoot )
136         throws Exception
137     {
138         final Path testCaseRootFrom = Paths.get( getBasedir(), "src/test/resources" ).resolve( Paths.get( "projects", sourcePath ) ) ;
139 
140         final Path testCaseRootTo = getWorkingDirectory( targetPath );
141 
142         // Recopy the test resources since they are modified in some tests
143         Files.walkFileTree( testCaseRootFrom, new SimpleFileVisitor<Path>() {
144 
145             @Override
146             public FileVisitResult visitFile( Path file, BasicFileAttributes attrs )
147                 throws IOException
148             {
149                 Path relPath = testCaseRootFrom.relativize( file );
150 
151                 if ( !relPath.toFile().getName().startsWith( "expected-" ) )
152                 {
153                   Files.createDirectories( testCaseRootTo.resolve( relPath ).getParent() );
154 
155                   Files.copy( file, testCaseRootTo.resolve( relPath ), StandardCopyOption.REPLACE_EXISTING );
156                 }
157 
158                 return FileVisitResult.CONTINUE;
159             }
160         });
161 
162         Path projectFile;
163         if ( executionRoot == null )
164         {
165             projectFile = testCaseRootTo.resolve( "pom.xml" );
166         }
167         else
168         {
169             projectFile = testCaseRootTo.resolve( Paths.get( executionRoot, "pom.xml" ) );
170         }
171 
172         List<ArtifactRepository> repos =
173             Collections.<ArtifactRepository>singletonList( new DefaultArtifactRepository( "central",
174                                                                                           getRemoteRepositoryURL(),
175                                                                                           new DefaultRepositoryLayout() ) );
176 
177         Repository repository = new Repository();
178         repository.setId( "central" );
179         repository.setUrl( getRemoteRepositoryURL() );
180 
181         Profile profile = new Profile();
182         profile.setId( "profile" );
183         profile.addRepository( repository );
184 
185         ProjectBuildingRequest buildingRequest = new DefaultProjectBuildingRequest();
186         buildingRequest.setLocalRepository( localRepository );
187         buildingRequest.setRemoteRepositories( repos );
188         buildingRequest.setPluginArtifactRepositories( repos );
189         buildingRequest.setRepositoryMerging( RepositoryMerging.REQUEST_DOMINANT );
190         MavenRepositorySystemSession repositorySession = new MavenRepositorySystemSession();
191         repositorySession.setLocalRepositoryManager( new SimpleLocalRepositoryManager( localRepository.getBasedir() ) );
192         buildingRequest.setRepositorySession( repositorySession );
193         buildingRequest.addProfile( profile );
194         buildingRequest.setActiveProfileIds( Arrays.asList( profile.getId() ) );
195         buildingRequest.setResolveDependencies( true );
196 
197         List<ProjectBuildingResult> buildingResults =
198             projectBuilder.build( Collections.singletonList( projectFile.toFile() ), true, buildingRequest );
199 
200         List<MavenProject> reactorProjects = new ArrayList<>();
201         for ( ProjectBuildingResult buildingResult : buildingResults )
202         {
203             reactorProjects.add( buildingResult.getProject() ) ;
204         }
205 
206         WorkspaceReader simpleReactorReader = new SimpleReactorWorkspaceReader( reactorProjects );
207         repositorySession.setWorkspaceReader( simpleReactorReader );
208 
209         ProjectSorter sorter = new ProjectSorter( reactorProjects );
210         reactorProjects = sorter.getSortedProjects();
211 
212         List<MavenProject> resolvedProjects = new ArrayList<>( reactorProjects.size() );
213         for ( MavenProject project  : reactorProjects )
214         {
215             MavenProject resolvedProject = projectBuilder.build( project.getFile(), buildingRequest ).getProject();
216             
217             // from LifecycleDependencyResolver
218             if ( project.getDependencyArtifacts() == null )
219             {
220                 try
221                 {
222                     resolvedProject.setDependencyArtifacts( resolvedProject.createArtifacts( artifactFactory, null, null ) );
223                 }
224                 catch ( InvalidDependencyVersionException e )
225                 {
226                     throw new LifecycleExecutionException( e );
227                 }
228             }
229             
230             resolvedProjects.add( resolvedProject );
231         }
232         return resolvedProjects;
233     }
234 
235     protected static Map<String,MavenProject> getProjectsAsMap( List<MavenProject> reactorProjects )
236     {
237         Map<String,MavenProject> map = new HashMap<>();
238         for ( MavenProject project : reactorProjects )
239         {
240             map.put( ArtifactUtils.versionlessKey( project.getGroupId(), project.getArtifactId() ), project );
241         }
242         return map;
243     }
244 
245     protected boolean comparePomFiles( List<MavenProject> reactorProjects )
246         throws IOException
247     {
248         return comparePomFiles( reactorProjects, true );
249     }
250 
251     protected boolean comparePomFiles( List<MavenProject> reactorProjects, boolean normalizeLineEndings )
252         throws IOException
253     {
254         comparePomFiles( reactorProjects, "", normalizeLineEndings );
255 
256         // TODO: return void since this is redundant
257         return true;
258     }
259 
260     protected void comparePomFiles( List<MavenProject> reactorProjects, String expectedFileSuffix )
261         throws IOException
262     {
263         comparePomFiles( reactorProjects, expectedFileSuffix, true );
264     }
265 
266     protected void comparePomFiles( List<MavenProject> reactorProjects, String expectedFileSuffix, boolean normalizeLineEndings )
267         throws IOException
268     {
269         for ( MavenProject project : reactorProjects )
270         {
271             comparePomFiles( project, expectedFileSuffix, normalizeLineEndings );
272         }
273     }
274 
275     protected void comparePomFiles( MavenProject project, String expectedFileSuffix )
276         throws IOException
277     {
278         comparePomFiles( project, expectedFileSuffix, true );
279     }
280 
281     protected void comparePomFiles( MavenProject project, String expectedFileSuffix, boolean normalizeLineEndings )
282         throws IOException
283     {
284         File actualFile = project.getFile();
285         File expectedFile = new File( actualFile.getParentFile(), "expected-pom" + expectedFileSuffix + ".xml" );
286 
287         comparePomFiles( expectedFile, actualFile, normalizeLineEndings, false );
288     }
289 
290     protected void comparePomFiles( File expectedFile, File actualFile )
291         throws IOException
292     {
293         comparePomFiles( expectedFile, actualFile, true, false );
294     }
295 
296     protected void comparePomFiles( File expectedFile, File actualFile, boolean normalizeLineEndings, boolean ignoreComments )
297         throws IOException
298     {
299         StringBuffer sb = new StringBuffer( "Check the transformed POM " + actualFile );
300         sb.append( SystemUtils.LINE_SEPARATOR );
301 
302         final String remoteRepositoryURL = getRemoteRepositoryURL();
303 
304         DiffBuilder diffBuilder = DiffBuilder.compare( expectedFile ).withTest( actualFile );
305         if ( normalizeLineEndings )
306         {
307             diffBuilder = diffBuilder.normalizeWhitespace();
308         }
309         if ( ignoreComments )
310         {
311             diffBuilder.ignoreComments();
312         }
313         // Order of elements has changed between M2 and M3, so match by name
314         diffBuilder.withNodeMatcher( new DefaultNodeMatcher( ElementSelectors.byName ) ).checkForSimilar();
315 
316         diffBuilder.withDifferenceEvaluator( new DifferenceEvaluator()
317         {
318             @Override
319             public ComparisonResult evaluate( Comparison comparison, ComparisonResult outcome )
320             {
321                 if ( "${remoterepo}".equals( comparison.getControlDetails().getValue() ) &&
322                                 remoteRepositoryURL.equals( comparison.getTestDetails().getValue() ) )
323                 {
324                     return ComparisonResult.EQUAL;
325                 }
326                 else if ( outcome == ComparisonResult.DIFFERENT
327                     && comparison.getType() == ComparisonType.CHILD_NODELIST_SEQUENCE )
328                 {
329                     // Order of elements has changed between M2 and M3
330                     return ComparisonResult.EQUAL;
331                 }
332                 else if ( outcome == ComparisonResult.DIFFERENT
333                                 && comparison.getType() == ComparisonType.TEXT_VALUE
334                                 && "${project.build.directory}/site".equals( comparison.getTestDetails().getValue() ) )
335                 {
336                     // M2 was target/site, M3 is ${project.build.directory}/site
337                     return ComparisonResult.EQUAL;
338                 }
339                 else
340                 {
341                     return outcome;
342                 }
343             }
344         } );
345 
346         Diff diff = diffBuilder.build();
347 
348         sb.append( diff.toString() );
349 
350         assertFalse( sb.toString(), diff.hasDifferences() );
351     }
352 
353     private String getRemoteRepositoryURL()
354       throws IOException
355     {
356         File testFile = getTestFile( "src/test/remote-repository" );
357         if (testFile.getAbsolutePath().equals( testFile.getCanonicalPath() ) )
358         {
359             return "file://" + getTestFile( "src/test/remote-repository" ).getAbsolutePath().replace( '\\', '/' );
360         }
361         return "file://" + getTestFile( "src/test/remote-repository" ).getCanonicalPath().replace( '\\', '/' );
362     }
363 
364     public static String getPath( File file )
365         throws IOException
366     {
367         return file.toPath().toRealPath( LinkOption.NOFOLLOW_LINKS ).toString();
368     }
369 
370     /**
371      * WorkspaceReader to find versions and artifacts from reactor
372      */
373     private static final class SimpleReactorWorkspaceReader
374         implements WorkspaceReader
375     {
376         private final List<MavenProject> reactorProjects;
377 
378         private SimpleReactorWorkspaceReader( List<MavenProject> reactorProjects )
379         {
380             this.reactorProjects = reactorProjects;
381         }
382 
383         @Override
384         public WorkspaceRepository getRepository()
385         {
386             return null;
387         }
388 
389         @Override
390         public List<String> findVersions( org.sonatype.aether.artifact.Artifact artifact )
391         {
392             for ( MavenProject mavenProject : reactorProjects )
393             {
394                 if ( Objects.equals( artifact.toString(), mavenProject.getArtifact().toString() ) )
395                 {
396                     return Collections.singletonList( mavenProject.getArtifact().getVersion() );
397                 }
398             }
399             return Collections.emptyList();
400         }
401 
402         @Override
403         public File findArtifact( org.sonatype.aether.artifact.Artifact artifact )
404         {
405             for ( MavenProject mavenProject : reactorProjects )
406             {
407                 String pom = mavenProject.getGroupId() + ':' + mavenProject.getArtifactId() + ":pom:"
408                     + mavenProject.getVersion();
409                 if ( Objects.equals( artifact.toString(), pom ) )
410                 {
411                     return mavenProject.getFile();
412                 }
413                 else if ( Objects.equals( artifact.toString(), mavenProject.getArtifact().toString() ) )
414                 {
415                     // just an existing, content doesn't matter
416                     return mavenProject.getFile();
417                 }
418             }
419             return null;
420         }
421     }
422 }