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