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