1 package org.apache.maven.archetype.mojos;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import org.apache.commons.collections.CollectionUtils;
23 import org.apache.maven.archetype.ArchetypeGenerationRequest;
24 import org.apache.maven.archetype.ArchetypeGenerationResult;
25 import org.apache.maven.archetype.common.Constants;
26 import org.apache.maven.archetype.exception.ArchetypeNotConfigured;
27 import org.apache.maven.archetype.generator.ArchetypeGenerator;
28 import org.apache.maven.plugin.AbstractMojo;
29 import org.apache.maven.plugin.MojoExecutionException;
30 import org.apache.maven.plugin.MojoFailureException;
31 import org.apache.maven.project.MavenProject;
32 import org.apache.maven.settings.Settings;
33 import org.apache.maven.shared.invoker.DefaultInvocationRequest;
34 import org.apache.maven.shared.invoker.InvocationRequest;
35 import org.apache.maven.shared.invoker.InvocationResult;
36 import org.apache.maven.shared.invoker.Invoker;
37 import org.apache.maven.shared.invoker.MavenInvocationException;
38 import org.apache.maven.shared.scriptinterpreter.RunFailureException;
39 import org.apache.maven.shared.scriptinterpreter.ScriptRunner;
40 import org.codehaus.plexus.util.FileUtils;
41 import org.codehaus.plexus.util.IOUtil;
42 import org.codehaus.plexus.util.InterpolationFilterReader;
43 import org.codehaus.plexus.util.ReaderFactory;
44 import org.codehaus.plexus.util.StringUtils;
45 import org.codehaus.plexus.util.WriterFactory;
46 import org.codehaus.plexus.util.introspection.ReflectionValueExtractor;
47
48 import java.io.File;
49 import java.io.FileInputStream;
50 import java.io.FileNotFoundException;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.Reader;
54 import java.io.StringWriter;
55 import java.io.Writer;
56 import java.util.Arrays;
57 import java.util.Collection;
58 import java.util.HashMap;
59 import java.util.LinkedHashMap;
60 import java.util.List;
61 import java.util.Map;
62 import java.util.Properties;
63 import java.util.Set;
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 public class IntegrationTestMojo
85 extends AbstractMojo
86 {
87
88
89
90 private ArchetypeGenerator archetypeGenerator;
91
92
93
94
95 private Invoker invoker;
96
97
98
99
100
101
102
103
104 private MavenProject project;
105
106
107
108
109
110
111
112 private boolean skip = false;
113
114
115
116
117
118
119
120
121
122 private File testProjectsDirectory;
123
124
125
126
127
128
129
130
131
132
133
134 private String postBuildHookScript;
135
136
137
138
139
140
141
142 private boolean noLog;
143
144
145
146
147
148
149
150 private boolean streamLogs;
151
152
153
154
155
156
157
158 private String encoding;
159
160
161
162
163
164
165
166
167
168 private File localRepositoryPath;
169
170
171
172
173
174
175
176 private boolean showVersion;
177
178
179
180
181
182
183
184 private boolean debug;
185
186
187
188
189
190
191
192 private Map<String, String> filterProperties;
193
194
195
196
197
198
199
200
201
202 private Settings settings;
203
204
205
206
207
208
209
210
211
212 private File settingsFile;
213
214
215 public void execute()
216 throws MojoExecutionException, MojoFailureException
217 {
218 if ( skip )
219 {
220 return;
221 }
222
223 if ( !testProjectsDirectory.exists() )
224 {
225 getLog().warn( "No Archetype IT projects: root 'projects' directory not found." );
226
227 return;
228 }
229
230 File archetypeFile = project.getArtifact().getFile();
231
232 if ( archetypeFile == null )
233 {
234 throw new MojoFailureException( "Unable to get the archetypes' artifact which should have just been built:"
235 + " you probably launched 'mvn archetype:integration-test' instead of"
236 + " 'mvn integration-test'." );
237 }
238
239 try
240 {
241 @SuppressWarnings( "unchecked" ) List<File> projectsGoalFiles =
242 FileUtils.getFiles( testProjectsDirectory, "*/goal.txt", "" );
243
244 if ( projectsGoalFiles.size() == 0 )
245 {
246 getLog().warn( "No Archetype IT projects: no directory with goal.txt found." );
247
248 return;
249 }
250
251 StringWriter errorWriter = new StringWriter();
252 for ( File goalFile : projectsGoalFiles )
253 {
254 try
255 {
256 processIntegrationTest( goalFile, archetypeFile );
257 }
258 catch ( IntegrationTestFailure ex )
259 {
260 errorWriter.write( "\nArchetype IT '" + goalFile.getParentFile().getName() + "' failed: " );
261 errorWriter.write( ex.getMessage() );
262 }
263 }
264
265 String errors = errorWriter.toString();
266 if ( !StringUtils.isEmpty( errors ) )
267 {
268 throw new MojoExecutionException( errors );
269 }
270 }
271 catch ( IOException ex )
272 {
273 throw new MojoFailureException( ex, ex.getMessage(), ex.getMessage() );
274 }
275 }
276
277
278
279
280
281
282
283
284 private void assertDirectoryEquals( File reference, File actual )
285 throws IntegrationTestFailure, IOException
286 {
287 @SuppressWarnings( "unchecked" ) List<String> referenceFiles =
288 FileUtils.getFileAndDirectoryNames( reference, "**", null, false, true, true, true );
289 getLog().debug( "reference content: " + referenceFiles );
290
291 @SuppressWarnings( "unchecked" ) List<String> actualFiles =
292 FileUtils.getFileAndDirectoryNames( actual, "**", null, false, true, true, true );
293 getLog().debug( "actual content: " + referenceFiles );
294
295 boolean fileNamesEquals = CollectionUtils.isEqualCollection( referenceFiles, actualFiles );
296
297 if ( !fileNamesEquals )
298 {
299 getLog().debug( "Actual list of files is not the same as reference:" );
300 int missing = 0;
301 for ( String ref : referenceFiles )
302 {
303 if ( actualFiles.contains( ref ) )
304 {
305 actualFiles.remove( ref );
306 getLog().debug( "Contained " + ref );
307 }
308 else
309 {
310 missing++;
311 getLog().error( "Not contained " + ref );
312 }
313 }
314 getLog().error( "Remains " + actualFiles );
315
316 throw new IntegrationTestFailure(
317 "Reference and generated project differs (missing: " + missing + ", unexpected: " + actualFiles.size()
318 + ")" );
319 }
320
321 boolean contentEquals = true;
322
323 for ( String file : referenceFiles )
324 {
325 File referenceFile = new File( reference, file );
326 File actualFile = new File( actual, file );
327
328 if ( referenceFile.isDirectory() )
329 {
330 if ( actualFile.isFile() )
331 {
332 getLog().warn( "File " + file + " is a directory in the reference but a file in actual" );
333 contentEquals = false;
334 }
335 }
336 else if ( actualFile.isDirectory() )
337 {
338 if ( referenceFile.isFile() )
339 {
340 getLog().warn( "File " + file + " is a file in the reference but a directory in actual" );
341 contentEquals = false;
342 }
343 }
344 else if ( !FileUtils.contentEquals( referenceFile, actualFile ) )
345 {
346 getLog().warn( "Contents of file " + file + " are not equal" );
347 contentEquals = false;
348 }
349 }
350 if ( !contentEquals )
351 {
352 throw new IntegrationTestFailure( "Some content are not equals" );
353 }
354 }
355
356 private Properties loadProperties( final File propertiesFile )
357 throws IOException, FileNotFoundException
358 {
359 Properties properties = new Properties();
360
361 InputStream in = null;
362 try
363 {
364 in = new FileInputStream( propertiesFile );
365
366 properties.load( in );
367 }
368 finally
369 {
370 IOUtil.close( in );
371 }
372
373 return properties;
374 }
375
376 private void processIntegrationTest( File goalFile, File archetypeFile )
377 throws IntegrationTestFailure, MojoExecutionException
378 {
379 getLog().info( "Processing Archetype IT project: " + goalFile.getParentFile().getName() );
380
381 try
382 {
383 Properties properties = getProperties( goalFile );
384
385 String basedir = goalFile.getParentFile().getPath() + "/project";
386
387 FileUtils.deleteDirectory( basedir );
388
389 FileUtils.mkdir( basedir );
390
391 ArchetypeGenerationRequest request =
392 new ArchetypeGenerationRequest().setArchetypeGroupId( project.getGroupId() ).setArchetypeArtifactId(
393 project.getArtifactId() ).setArchetypeVersion( project.getVersion() ).setGroupId(
394 properties.getProperty( Constants.GROUP_ID ) ).setArtifactId(
395 properties.getProperty( Constants.ARTIFACT_ID ) ).setVersion(
396 properties.getProperty( Constants.VERSION ) ).setPackage(
397 properties.getProperty( Constants.PACKAGE ) ).setOutputDirectory( basedir ).setProperties(
398 properties );
399
400 ArchetypeGenerationResult result = new ArchetypeGenerationResult();
401
402 archetypeGenerator.generateArchetype( request, archetypeFile, result );
403
404 if ( result.getCause() != null )
405 {
406 if ( result.getCause() instanceof ArchetypeNotConfigured )
407 {
408 ArchetypeNotConfigured anc = (ArchetypeNotConfigured) result.getCause();
409
410 throw new IntegrationTestFailure(
411 "Missing required properties in archetype.properties: " + StringUtils.join(
412 anc.getMissingProperties().iterator(), ", " ), anc );
413 }
414
415 throw new IntegrationTestFailure( result.getCause().getMessage(), result.getCause() );
416 }
417
418 File reference = new File( goalFile.getParentFile(), "reference" );
419
420 if ( reference.exists() )
421 {
422
423 getLog().info( "Comparing generated project with reference content: " + reference );
424
425 assertDirectoryEquals( reference, new File( basedir, request.getArtifactId() ) );
426 }
427
428 String goals = FileUtils.fileRead( goalFile );
429
430 invokePostArchetypeGenerationGoals( goals, new File( basedir, request.getArtifactId() ), goalFile );
431 }
432 catch ( IOException ioe )
433 {
434 throw new IntegrationTestFailure( ioe );
435 }
436 }
437
438 private Properties getProperties( File goalFile )
439 throws IOException
440 {
441 File propertiesFile = new File( goalFile.getParentFile(), "archetype.properties" );
442
443 return loadProperties( propertiesFile );
444 }
445
446 private void invokePostArchetypeGenerationGoals( String goals, File basedir, File goalFile )
447 throws IntegrationTestFailure, IOException, MojoExecutionException
448 {
449 FileLogger logger = setupLogger( basedir );
450
451 if ( !StringUtils.isBlank( goals ) )
452 {
453
454 getLog().info( "Invoking post-archetype-generation goals: " + goals );
455
456 if ( !localRepositoryPath.exists() )
457 {
458 localRepositoryPath.mkdirs();
459 }
460
461 InvocationRequest request = new DefaultInvocationRequest().setBaseDirectory( basedir ).setGoals(
462 Arrays.asList( StringUtils.split( goals, "," ) ) ).setLocalRepositoryDirectory(
463 localRepositoryPath ).setInteractive( false ).setShowErrors( true );
464
465 request.setDebug( debug );
466
467 request.setShowVersion( showVersion );
468
469 if ( logger != null )
470 {
471 request.setErrorHandler( logger );
472
473 request.setOutputHandler( logger );
474 }
475
476 File interpolatedSettingsFile = null;
477 if ( settingsFile != null )
478 {
479 File interpolatedSettingsDirectory =
480 new File( project.getBuild().getOutputDirectory(), "archetype-it" );
481 if ( interpolatedSettingsDirectory.exists() )
482 {
483 FileUtils.deleteDirectory( interpolatedSettingsDirectory );
484 }
485 interpolatedSettingsDirectory.mkdir();
486 interpolatedSettingsFile =
487 new File( interpolatedSettingsDirectory, "interpolated-" + settingsFile.getName() );
488
489 buildInterpolatedFile( settingsFile, interpolatedSettingsFile );
490
491 request.setUserSettingsFile( interpolatedSettingsFile );
492 }
493
494 try
495 {
496 InvocationResult result = invoker.execute( request );
497
498 getLog().info( "Post-archetype-generation invoker exit code: " + result.getExitCode() );
499
500 if ( result.getExitCode() != 0 )
501 {
502 throw new IntegrationTestFailure( "Execution failure: exit code = " + result.getExitCode(),
503 result.getExecutionException() );
504 }
505 }
506 catch ( MavenInvocationException e )
507 {
508 throw new IntegrationTestFailure( "Cannot run additions goals.", e );
509 }
510 }
511 else
512 {
513 getLog().info( "No post-archetype-generation goals to invoke." );
514 }
515
516 ScriptRunner scriptRunner = new ScriptRunner( getLog() );
517 scriptRunner.setScriptEncoding( encoding );
518
519 Map<String, Object> context = new LinkedHashMap<String, Object>();
520 context.put( "projectDir", basedir );
521
522 try
523 {
524 scriptRunner.run( "post-build script", goalFile.getParentFile(), postBuildHookScript, context, logger,
525 "failure post script", true );
526 }
527 catch ( RunFailureException e )
528 {
529 throw new IntegrationTestFailure( "post build script failure failure: " + e.getMessage(), e );
530 }
531 }
532
533 private FileLogger setupLogger( File basedir )
534 throws IOException
535 {
536 FileLogger logger = null;
537
538 if ( !noLog )
539 {
540 File outputLog = new File( basedir, "build.log" );
541
542 if ( streamLogs )
543 {
544 logger = new FileLogger( outputLog, getLog() );
545 }
546 else
547 {
548 logger = new FileLogger( outputLog );
549 }
550
551 getLog().debug( "build log initialized in: " + outputLog );
552
553 }
554
555 return logger;
556 }
557
558 class IntegrationTestFailure
559 extends Exception
560 {
561 IntegrationTestFailure()
562 {
563 super();
564 }
565
566 IntegrationTestFailure( String message )
567 {
568 super( message );
569 }
570
571 IntegrationTestFailure( Throwable cause )
572 {
573 super( cause );
574 }
575
576 IntegrationTestFailure( String message, Throwable cause )
577 {
578 super( message, cause );
579 }
580 }
581
582
583
584
585
586
587 private Map<String, Object> getInterpolationValueSource()
588 {
589 Map<String, Object> props = new HashMap<String, Object>();
590 if ( filterProperties != null )
591 {
592 props.putAll( filterProperties );
593 }
594 if ( filterProperties != null )
595 {
596 props.putAll( filterProperties );
597 }
598 props.put( "basedir", this.project.getBasedir().getAbsolutePath() );
599 props.put( "baseurl", toUrl( this.project.getBasedir().getAbsolutePath() ) );
600 if ( settings.getLocalRepository() != null )
601 {
602 props.put( "localRepository", settings.getLocalRepository() );
603 props.put( "localRepositoryUrl", toUrl( settings.getLocalRepository() ) );
604 }
605 return new CompositeMap( this.project, props );
606 }
607
608 protected void buildInterpolatedFile( File originalFile, File interpolatedFile )
609 throws MojoExecutionException
610 {
611 getLog().debug( "Interpolate " + originalFile.getPath() + " to " + interpolatedFile.getPath() );
612
613 try
614 {
615 String xml;
616
617 Reader reader = null;
618 try
619 {
620
621 Map<String, Object> composite = getInterpolationValueSource();
622 reader = ReaderFactory.newXmlReader( originalFile );
623 reader = new InterpolationFilterReader( reader, composite, "@", "@" );
624 xml = IOUtil.toString( reader );
625 }
626 finally
627 {
628 IOUtil.close( reader );
629 }
630
631 Writer writer = null;
632 try
633 {
634 interpolatedFile.getParentFile().mkdirs();
635 writer = WriterFactory.newXmlWriter( interpolatedFile );
636 writer.write( xml );
637 writer.flush();
638 }
639 finally
640 {
641 IOUtil.close( writer );
642 }
643 }
644 catch ( IOException e )
645 {
646 throw new MojoExecutionException( "Failed to interpolate file " + originalFile.getPath(), e );
647 }
648 }
649
650 private static class CompositeMap
651 implements Map<String, Object>
652 {
653
654
655
656
657 private MavenProject mavenProject;
658
659
660
661
662 private Map<String, Object> properties;
663
664
665
666
667
668
669
670
671 protected CompositeMap( MavenProject mavenProject, Map<String, Object> properties )
672 {
673 if ( mavenProject == null )
674 {
675 throw new IllegalArgumentException( "no project specified" );
676 }
677 this.mavenProject = mavenProject;
678 this.properties = properties == null ? (Map) new Properties() : properties;
679 }
680
681
682
683
684
685
686 public void clear()
687 {
688
689 }
690
691
692
693
694
695
696 public boolean containsKey( Object key )
697 {
698 if ( !( key instanceof String ) )
699 {
700 return false;
701 }
702
703 String expression = (String) key;
704 if ( expression.startsWith( "project." ) || expression.startsWith( "pom." ) )
705 {
706 try
707 {
708 Object evaluated = ReflectionValueExtractor.evaluate( expression, this.mavenProject );
709 if ( evaluated != null )
710 {
711 return true;
712 }
713 }
714 catch ( Exception e )
715 {
716
717 }
718 }
719
720 return properties.containsKey( key ) || mavenProject.getProperties().containsKey( key );
721 }
722
723
724
725
726
727
728 public boolean containsValue( Object value )
729 {
730 throw new UnsupportedOperationException();
731 }
732
733
734
735
736
737
738 public Set<Entry<String, Object>> entrySet()
739 {
740 throw new UnsupportedOperationException();
741 }
742
743
744
745
746
747
748 public Object get( Object key )
749 {
750 if ( !( key instanceof String ) )
751 {
752 return null;
753 }
754
755 String expression = (String) key;
756 if ( expression.startsWith( "project." ) || expression.startsWith( "pom." ) )
757 {
758 try
759 {
760 Object evaluated = ReflectionValueExtractor.evaluate( expression, this.mavenProject );
761 if ( evaluated != null )
762 {
763 return evaluated;
764 }
765 }
766 catch ( Exception e )
767 {
768
769 }
770 }
771
772 Object value = properties.get( key );
773
774 return ( value != null ? value : this.mavenProject.getProperties().get( key ) );
775
776 }
777
778
779
780
781
782
783 public boolean isEmpty()
784 {
785 return this.mavenProject == null && this.mavenProject.getProperties().isEmpty()
786 && this.properties.isEmpty();
787 }
788
789
790
791
792
793
794 public Set<String> keySet()
795 {
796 throw new UnsupportedOperationException();
797 }
798
799
800
801
802
803
804 public Object put( String key, Object value )
805 {
806 throw new UnsupportedOperationException();
807 }
808
809
810
811
812
813
814 public void putAll( Map<? extends String, ? extends Object> t )
815 {
816 throw new UnsupportedOperationException();
817 }
818
819
820
821
822
823
824 public Object remove( Object key )
825 {
826 throw new UnsupportedOperationException();
827 }
828
829
830
831
832
833
834 public int size()
835 {
836 throw new UnsupportedOperationException();
837 }
838
839
840
841
842
843
844 public Collection<Object> values()
845 {
846 throw new UnsupportedOperationException();
847 }
848 }
849
850
851
852
853
854
855
856
857
858 private static String toUrl( String filename )
859 {
860
861
862
863
864 String url = "file://" + new File( filename ).toURI().getPath();
865 if ( url.endsWith( "/" ) )
866 {
867 url = url.substring( 0, url.length() - 1 );
868 }
869 return url;
870 }
871 }