View Javadoc

1   package org.apache.maven.shared.test.plugin;
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 java.io.File;
23  import java.io.IOException;
24  import java.io.Reader;
25  import java.io.Writer;
26  import java.util.ArrayList;
27  import java.util.List;
28  import java.util.Properties;
29  
30  import org.apache.maven.artifact.Artifact;
31  import org.apache.maven.artifact.UnknownRepositoryLayoutException;
32  import org.apache.maven.artifact.factory.ArtifactFactory;
33  import org.apache.maven.artifact.handler.ArtifactHandler;
34  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
35  import org.apache.maven.artifact.repository.ArtifactRepository;
36  import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
37  import org.apache.maven.model.Build;
38  import org.apache.maven.model.DeploymentRepository;
39  import org.apache.maven.model.DistributionManagement;
40  import org.apache.maven.model.Model;
41  import org.apache.maven.model.Plugin;
42  import org.apache.maven.model.Repository;
43  import org.apache.maven.model.Site;
44  import org.apache.maven.model.io.xpp3.MavenXpp3Reader;
45  import org.apache.maven.model.io.xpp3.MavenXpp3Writer;
46  import org.apache.maven.project.DefaultProjectBuildingRequest;
47  import org.apache.maven.project.MavenProject;
48  import org.apache.maven.project.ProjectBuilder;
49  import org.apache.maven.project.ProjectBuildingException;
50  import org.apache.maven.project.ProjectBuildingRequest;
51  import org.apache.maven.project.artifact.ProjectArtifactMetadata;
52  import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
53  import org.codehaus.plexus.component.annotations.Component;
54  import org.codehaus.plexus.component.annotations.Requirement;
55  import org.codehaus.plexus.util.FileUtils;
56  import org.codehaus.plexus.util.IOUtil;
57  import org.codehaus.plexus.util.ReaderFactory;
58  import org.codehaus.plexus.util.WriterFactory;
59  import org.codehaus.plexus.util.xml.Xpp3Dom;
60  import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
61  
62  /**
63   * Testing tool used to read MavenProject instances from pom.xml files, and to create plugin jar
64   * files (package phase of the normal build process) for distribution to a test local repository
65   * directory.
66   *
67   * @author jdcasey
68   * @version $Id$
69   */
70  @Component( role = ProjectTool.class )
71  public class ProjectTool
72  {
73      /** Plexus role */
74      public static final String ROLE = ProjectTool.class.getName();
75  
76      public static final String INTEGRATION_TEST_DEPLOYMENT_REPO_URL = "integration-test.deployment.repo.url";
77  
78      @Requirement
79      private BuildTool buildTool;
80  
81      @Requirement
82      private RepositoryTool repositoryTool;
83  
84      @Requirement
85      private ProjectBuilder projectBuilder;
86  
87      @Requirement
88      private ArtifactHandlerManager artifactHandlerManager;
89  
90      @Requirement
91      private ArtifactFactory artifactFactory;
92  
93      @Requirement
94      private ArtifactRepositoryFactory artifactRepositoryFactory;
95  
96      /**
97       * Construct a MavenProject instance from the specified POM file.
98       *
99       * @param pomFile current POM file
100      * @return the Maven project from a file
101      * @throws TestToolsException if any
102      */
103     public MavenProject readProject( File pomFile )
104         throws TestToolsException
105     {
106         return readProject( pomFile, repositoryTool.findLocalRepositoryDirectory() );
107     }
108 
109     /**
110      * Construct a MavenProject instance from the specified POM file, using the specified local
111      * repository directory to resolve ancestor POMs as needed.
112      *
113      * @param pomFile current POM file
114      * @param localRepositoryBasedir
115      * @return the Maven project from a file and a local repo
116      * @throws TestToolsException if any
117      */
118     public MavenProject readProject( File pomFile, File localRepositoryBasedir )
119         throws TestToolsException
120     {
121         try
122         {
123             ArtifactRepository localRepository = repositoryTool
124                 .createLocalArtifactRepositoryInstance( localRepositoryBasedir );
125 
126             ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
127             return projectBuilder.build( pomFile, request ).getProject();
128         }
129         catch ( ProjectBuildingException e )
130         {
131             throw new TestToolsException( "Error building MavenProject instance from test pom: " + pomFile, e );
132         }
133     }
134 
135     /**
136      * Construct a MavenProject instance from the specified POM file with dependencies.
137      *
138      * @param pomFile current POM file
139      * @return the Maven project with dependencies from a file
140      * @throws TestToolsException if any
141      */
142     public MavenProject readProjectWithDependencies( File pomFile )
143         throws TestToolsException
144     {
145         return readProjectWithDependencies( pomFile, repositoryTool.findLocalRepositoryDirectory() );
146     }
147 
148     /**
149      * Construct a MavenProject instance from the specified POM file with dependencies, using the specified local
150      * repository directory to resolve ancestor POMs as needed.
151      *
152      * @param pomFile current POM file
153      * @param localRepositoryBasedir
154      * @return the Maven project with dependencies from a file and a local repo
155      * @throws TestToolsException if any
156      */
157     public MavenProject readProjectWithDependencies( File pomFile, File localRepositoryBasedir )
158         throws TestToolsException
159     {
160         try
161         {
162             ArtifactRepository localRepository = repositoryTool
163                 .createLocalArtifactRepositoryInstance( localRepositoryBasedir );
164 
165             ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
166             return projectBuilder.build( pomFile, request ).getProject();
167         }
168         catch ( ProjectBuildingException e )
169         {
170             throw new TestToolsException( "Error building MavenProject instance from test pom: " + pomFile, e );
171         }
172     }
173 
174     /**
175      * Run the plugin's Maven build up to the package phase, in order to produce a jar file for
176      * distribution to a test-time local repository. The testVersion parameter specifies the version
177      * to be used in the <version/> element of the plugin configuration, and also in fully
178      * qualified, unambiguous goal invocations (as in
179      * org.apache.maven.plugins:maven-eclipse-plugin:test:eclipse).
180      *
181      * @param pomFile The plugin's POM
182      * @param testVersion The version to use for testing this plugin. To promote test resiliency,
183      *   this version should remain unchanged, regardless of what plugin version is under
184      *   development.
185      * @param skipUnitTests In cases where test builds occur during the unit-testing phase (usually
186      *   a bad testing smell), the plugin jar must be produced <b>without</b> running unit tests.
187      *   Otherwise, the testing process will result in a recursive loop of building a plugin jar and
188      *   trying to unit test it during the build. In these cases, set this flag to <code>true</code>.
189      * @return The resulting MavenProject, after the test version and skip flag (for unit tests)
190      *   have been appropriately configured.
191      * @throws TestToolsException if any
192      */
193     public MavenProject packageProjectArtifact( File pomFile, String testVersion, boolean skipUnitTests )
194         throws TestToolsException
195     {
196         return packageProjectArtifact( pomFile, testVersion, skipUnitTests, null );
197     }
198 
199     /**
200      * Run the plugin's Maven build up to the package phase, in order to produce a jar file for
201      * distribution to a test-time local repository. The testVersion parameter specifies the version
202      * to be used in the &lt;version/&gt; element of the plugin configuration, and also in fully
203      * qualified, unambiguous goal invocations (as in
204      * org.apache.maven.plugins:maven-eclipse-plugin:test:eclipse).
205      *
206      * @param pomFile The plugin's POM
207      * @param testVersion The version to use for testing this plugin. To promote test resiliency,
208      *   this version should remain unchanged, regardless of what plugin version is under
209      *   development.
210      * @param skipUnitTests In cases where test builds occur during the unit-testing phase (usually
211      *   a bad testing smell), the plugin jar must be produced <b>without</b> running unit tests.
212      *   Otherwise, the testing process will result in a recursive loop of building a plugin jar and
213      *   trying to unit test it during the build. In these cases, set this flag to <code>true</code>.
214      * @param logFile The file to which build output should be logged, in order to allow later
215      *   inspection in case this build fails.
216      * @return The resulting MavenProject, after the test version and skip flag (for unit tests)
217      *   have been appropriately configured.
218      * @throws TestToolsException if any
219      */
220     public MavenProject packageProjectArtifact( File pomFile, String testVersion, boolean skipUnitTests, File logFile )
221         throws TestToolsException
222     {
223         PomInfo pomInfo = manglePomForTesting( pomFile, testVersion, skipUnitTests );
224 
225         Properties properties = new Properties();
226 
227         List<String> goals = new ArrayList<String>();
228         goals.add( "package" );
229 
230         File buildLog = logFile == null ? pomInfo.getBuildLogFile() : logFile;
231         System.out.println( "Now Building test version of the plugin...\nUsing staged plugin-pom: "
232             + pomInfo.getPomFile().getAbsolutePath() );
233 
234         buildTool.executeMaven( pomInfo.getPomFile(), properties, goals, buildLog );
235 
236         File artifactFile = new File( pomInfo.getPomFile().getParentFile(),
237                                       pomInfo.getBuildDirectory() + "/" + pomInfo.getFinalName() );
238         System.out.println( "Using IT Plugin Jar: " + artifactFile.getAbsolutePath() );
239         try
240         {
241             ProjectBuildingRequest request = new DefaultProjectBuildingRequest();
242             request.setLocalRepository( artifactRepositoryFactory.createArtifactRepository( "local", new File( "target/localrepo" ).getCanonicalFile().toURL().toExternalForm(), "default", null, null ) );
243             request.setRepositorySession( MavenRepositorySystemUtils.newSession() );
244             MavenProject project = projectBuilder.build( pomInfo.getPomFile(), request ).getProject();
245 
246             Artifact artifact = artifactFactory.createArtifact( project.getGroupId(), project.getArtifactId(), project
247                 .getVersion(), null, project.getPackaging() );
248 
249             artifact.setFile( artifactFile );
250             artifact.addMetadata( new ProjectArtifactMetadata( artifact, project.getFile() ) );
251 
252             project.setArtifact( artifact );
253 
254             return project;
255         }
256         catch ( ProjectBuildingException e )
257         {
258             throw new TestToolsException(
259                                           "Error building MavenProject instance from test pom: " + pomInfo.getPomFile(),
260                                           e );
261         }
262         catch ( UnknownRepositoryLayoutException e )
263         {
264             throw new TestToolsException(
265                                          "Error building ArtifactRepository instance from test pom: " + pomInfo.getPomFile(),
266                                          e );
267         }
268         catch ( IOException e )
269         {
270             throw new TestToolsException(
271                                          "Error building ArtifactRepository instance from test pom: " + pomInfo.getPomFile(),
272                                          e );
273         }
274     }
275 
276     /**
277      * Inject a special version for testing, to allow tests to unambiguously reference the plugin
278      * currently under test. If test builds will be executed from the unit-testing phase, also inject
279      * &lt;skip&gt;true&lt;/skip&gt; into the configuration of the <code>maven-surefire-plugin</code>
280      * to allow production of a test-only version of the plugin jar without running unit tests.
281      *
282      * @param pomFile The plugin POM
283      * @param testVersion The version that allows test builds to reference the plugin under test
284      * @param skipUnitTests If true, configure the surefire plugin to skip unit tests
285      * @return Information about mangled POM, including the temporary file to which it was written.
286      * @throws TestToolsException if any
287      */
288     protected PomInfo manglePomForTesting( File pomFile, String testVersion, boolean skipUnitTests )
289         throws TestToolsException
290     {
291         File input = pomFile;
292 
293         File output = new File( pomFile.getParentFile(), "pom-" + testVersion + ".xml" );
294         output.deleteOnExit();
295 
296         Reader reader = null;
297         Writer writer = null;
298 
299         Model model = null;
300         String finalName = null;
301         String buildDirectory = null;
302 
303         try
304         {
305             reader = ReaderFactory.newXmlReader( input );
306 
307             model = new MavenXpp3Reader().read( reader );
308         }
309         catch ( IOException e )
310         {
311             throw new TestToolsException( "Error creating test-time version of POM for: " + input, e );
312         }
313         catch ( XmlPullParserException e )
314         {
315             throw new TestToolsException( "Error creating test-time version of POM for: " + input, e );
316         }
317         finally
318         {
319             IOUtil.close( reader );
320         }
321 
322         try
323         {
324             model.setVersion( testVersion );
325 
326             Build build = model.getBuild();
327             if ( build == null )
328             {
329                 build = new Build();
330             }
331             buildDirectory = build.getDirectory();
332 
333             if ( buildDirectory == null )
334             {
335                 buildDirectory = "target";
336             }
337 
338             buildDirectory = ( buildDirectory + File.separatorChar + "it-build-target" );
339             build.setDirectory( buildDirectory );
340             build.setOutputDirectory( buildDirectory + File.separatorChar + "classes" );
341             System.out.println( "Using " + build.getDirectory() + " and " + build.getOutputDirectory()
342                 + " to build IT version of plugin" );
343             model.setBuild( build );
344 
345             finalName = build.getFinalName();
346 
347             if ( finalName == null )
348             {
349                 ArtifactHandler handler = artifactHandlerManager.getArtifactHandler( model.getPackaging() );
350 
351                 String ext = handler.getExtension();
352 
353                 finalName = model.getArtifactId() + "-" + model.getVersion() + "." + ext;
354             }
355 
356             DistributionManagement distMgmt = new DistributionManagement();
357 
358             DeploymentRepository deployRepo = new DeploymentRepository();
359 
360             deployRepo.setId( "integration-test.output" );
361 
362             File tmpDir = FileUtils.createTempFile( "integration-test-repo", "", null );
363             String tmpUrl = tmpDir.toURL().toExternalForm();
364 
365             deployRepo.setUrl( tmpUrl );
366 
367             distMgmt.setRepository( deployRepo );
368             distMgmt.setSnapshotRepository( deployRepo );
369 
370             Repository localAsRemote = new Repository();
371             localAsRemote.setId( "testing.mainLocalAsRemote" );
372 
373             File localRepoDir = repositoryTool.findLocalRepositoryDirectory();
374             localAsRemote.setUrl( localRepoDir.toURL().toExternalForm() );
375 
376             model.addRepository( localAsRemote );
377             model.addPluginRepository( localAsRemote );
378 
379             Site site = new Site();
380 
381             site.setId( "integration-test.output" );
382             site.setUrl( tmpUrl );
383 
384             distMgmt.setSite( site );
385 
386             model.setDistributionManagement( distMgmt );
387 
388             model.addProperty( INTEGRATION_TEST_DEPLOYMENT_REPO_URL, tmpUrl );
389 
390             if ( skipUnitTests )
391             {
392                 List<Plugin> plugins = build.getPlugins();
393                 Plugin plugin = null;
394                 for ( Plugin plug : plugins )
395                 {
396                     if ( "maven-surefire-plugin".equals( plug.getArtifactId() ) )
397                     {
398                         plugin = plug;
399                         break;
400                     }
401                 }
402 
403                 if ( plugin == null )
404                 {
405                     plugin = new Plugin();
406                     plugin.setArtifactId( "maven-surefire-plugin" );
407                     build.addPlugin( plugin );
408                 }
409 
410                 Xpp3Dom configDom = (Xpp3Dom) plugin.getConfiguration();
411                 if ( configDom == null )
412                 {
413                     configDom = new Xpp3Dom( "configuration" );
414                     plugin.setConfiguration( configDom );
415                 }
416 
417                 Xpp3Dom skipDom = new Xpp3Dom( "skip" );
418                 skipDom.setValue( "true" );
419 
420                 configDom.addChild( skipDom );
421             }
422 
423             writer = WriterFactory.newXmlWriter( output );
424 
425             new MavenXpp3Writer().write( writer, model );
426         }
427         catch ( IOException e )
428         {
429             throw new TestToolsException( "Error creating test-time version of POM for: " + input, e );
430         }
431         finally
432         {
433             IOUtil.close( writer );
434         }
435 
436         return new PomInfo( output, model.getGroupId(), model.getArtifactId(), model.getVersion(),
437                             model.getBuild().getDirectory(), model.getBuild().getOutputDirectory(), finalName );
438     }
439 
440     static final class PomInfo
441     {
442         private final File pomFile;
443 
444         private final String groupId;
445 
446         private final String artifactId;
447 
448         private final String version;
449 
450         private final String finalName;
451 
452         private final String buildDirectory;
453 
454         private final String buildOutputDirectory;
455 
456         PomInfo( File pomFile, String groupId, String artifactId, String version, String buildDirectory,
457                  String buildOutputDirectory, String finalName )
458         {
459             this.pomFile = pomFile;
460             this.groupId = groupId;
461             this.artifactId = artifactId;
462             this.version = version;
463             this.buildDirectory = buildDirectory;
464             this.buildOutputDirectory = buildOutputDirectory;
465             this.finalName = finalName;
466         }
467 
468         File getPomFile()
469         {
470             return pomFile;
471         }
472 
473         String getBuildDirectory()
474         {
475             return buildDirectory;
476         }
477 
478         String getBuildOutputDirectory()
479         {
480             return buildOutputDirectory;
481         }
482 
483         String getFinalName()
484         {
485             return finalName;
486         }
487 
488         File getBuildLogFile()
489         {
490             return new File( buildDirectory + "/test-build-logs/" + groupId + "_" + artifactId + "_" + version
491                 + ".build.log" );
492         }
493     }
494 }