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