View Javadoc
1   package org.apache.maven.it;
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.BufferedReader;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileReader;
28  import java.io.FileWriter;
29  import java.io.FilenameFilter;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.InputStreamReader;
33  import java.io.PrintStream;
34  import java.io.Writer;
35  import java.net.MalformedURLException;
36  import java.net.URL;
37  import java.nio.file.Files;
38  import java.nio.file.Paths;
39  import java.text.DecimalFormat;
40  import java.text.NumberFormat;
41  import java.util.ArrayList;
42  import java.util.Arrays;
43  import java.util.Collections;
44  import java.util.HashMap;
45  import java.util.List;
46  import java.util.Map;
47  import java.util.Properties;
48  import java.util.StringTokenizer;
49  import java.util.regex.Pattern;
50  
51  import javax.xml.parsers.ParserConfigurationException;
52  import javax.xml.parsers.SAXParser;
53  import javax.xml.parsers.SAXParserFactory;
54  
55  import org.apache.maven.shared.utils.StringUtils;
56  import org.apache.maven.shared.utils.cli.CommandLineException;
57  import org.apache.maven.shared.utils.cli.CommandLineUtils;
58  import org.apache.maven.shared.utils.cli.Commandline;
59  import org.apache.maven.shared.utils.cli.StreamConsumer;
60  import org.apache.maven.shared.utils.cli.WriterStreamConsumer;
61  import org.apache.maven.shared.utils.io.FileUtils;
62  import org.junit.Assert;
63  import org.xml.sax.InputSource;
64  import org.xml.sax.SAXException;
65  import org.xml.sax.SAXParseException;
66  import org.xml.sax.helpers.DefaultHandler;
67  
68  /**
69   * @author Jason van Zyl
70   * @author <a href="mailto:brett@apache.org">Brett Porter</a>
71   */
72  public class Verifier
73  {
74      private static final String LOG_FILENAME = "log.txt";
75  
76      private static final String[] DEFAULT_CLI_OPTIONS = {"-e", "--batch-mode"};
77  
78      private String localRepo;
79  
80      private final String basedir;
81  
82      private final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
83  
84      private final ByteArrayOutputStream errStream = new ByteArrayOutputStream();
85  
86      private final String[] defaultCliOptions;
87  
88      private PrintStream originalOut;
89  
90      private PrintStream originalErr;
91  
92      private List<String> cliOptions = new ArrayList<String>();
93  
94      private Properties systemProperties = new Properties();
95  
96      private Map<String, String> environmentVariables = new HashMap<String, String>();
97  
98      private Properties verifierProperties = new Properties();
99  
100     private boolean autoclean = true;
101 
102     private String localRepoLayout = "default";
103 
104     private boolean debug;
105 
106     /** 
107      * If {@code true} uses {@link ForkedLauncher}, if {@code false} uses {@link Embedded3xLauncher},
108      * otherwise considers the value {@link #forkMode}.
109      */
110     private Boolean forkJvm;
111 
112     private String logFileName = LOG_FILENAME;
113 
114     private String mavenHome;
115 
116     // will launch mvn with --debug 
117     private boolean mavenDebug = false;
118 
119     /**
120      * Either "auto" (use {@link ForkedLauncher} when {@link #environmentVariables} is not empty,
121      * otherwise use {@link Embedded3xLauncher}) , "embedder" (always use {@link Embedded3xLauncher})
122      * or something else (always use {@link ForkedLauncher}).
123      * Set through system property {@code verifier.forkMode}.
124      * Only relevant if {@link #forkJvm} is {@code null}.
125      */
126     private String forkMode;
127 
128     private boolean debugJvm = false;
129 
130     private boolean useWrapper;
131 
132     private static MavenLauncher embeddedLauncher;
133 
134     public Verifier( String basedir )
135         throws VerificationException
136     {
137         this( basedir, null );
138     }
139 
140     public Verifier( String basedir, boolean debug )
141         throws VerificationException
142     {
143         this( basedir, null, debug );
144     }
145 
146     public Verifier( String basedir, String settingsFile )
147         throws VerificationException
148     {
149         this( basedir, settingsFile, false );
150     }
151 
152     public Verifier( String basedir, String settingsFile, boolean debug )
153         throws VerificationException
154     {
155         this( basedir, settingsFile, debug, DEFAULT_CLI_OPTIONS );
156     }
157 
158     public Verifier( String basedir, String settingsFile, boolean debug, String[] defaultCliOptions )
159         throws VerificationException
160     {
161         this( basedir, settingsFile, debug, null, defaultCliOptions );
162     }
163 
164     public Verifier( String basedir, String settingsFile, boolean debug, boolean forkJvm )
165         throws VerificationException
166     {
167         this( basedir, settingsFile, debug, forkJvm, DEFAULT_CLI_OPTIONS );
168     }
169 
170     public Verifier( String basedir, String settingsFile, boolean debug, boolean forkJvm, String[] defaultCliOptions )
171         throws VerificationException
172     {
173         this( basedir, settingsFile, debug, forkJvm, defaultCliOptions, null );
174     }
175 
176     public Verifier( String basedir, String settingsFile, boolean debug, String mavenHome ) 
177             throws VerificationException
178     {
179         this( basedir, settingsFile, debug, null, DEFAULT_CLI_OPTIONS, mavenHome );
180     }
181 
182     public Verifier( String basedir, String settingsFile, boolean debug, String mavenHome, String[] defaultCliOptions ) 
183         throws VerificationException
184     {
185         this( basedir, settingsFile, debug, null, defaultCliOptions, mavenHome );
186     }
187 
188     private Verifier( String basedir, String settingsFile, boolean debug, Boolean forkJvm, String[] defaultCliOptions,
189             String mavenHome ) throws VerificationException
190     {
191         this.basedir = basedir;
192 
193         this.forkJvm = forkJvm;
194         this.forkMode = System.getProperty( "verifier.forkMode" );
195 
196         if ( !debug )
197         {
198             originalOut = System.out;
199 
200             originalErr = System.err;
201         }
202 
203         setDebug( debug );
204 
205         findLocalRepo( settingsFile );
206         if ( mavenHome == null )
207         {
208             this.mavenHome = getDefaultMavenHome();
209             useWrapper = Files.exists( Paths.get( getBasedir(), "mvnw" ) );
210         }
211         else
212         {
213             this.mavenHome = mavenHome;
214             useWrapper = false;
215         }
216 
217         if ( StringUtils.isEmpty( mavenHome ) && StringUtils.isEmpty( forkMode ) )
218         {
219             forkMode = "auto";
220         }
221 
222         this.defaultCliOptions = defaultCliOptions == null ? new String[0] : defaultCliOptions.clone();
223     }
224 
225     private static String getDefaultMavenHome()
226     {
227         String defaultMavenHome = System.getProperty( "maven.home" );
228 
229         if ( defaultMavenHome == null )
230         {
231             Properties envVars = CommandLineUtils.getSystemEnvVars();
232             defaultMavenHome = envVars.getProperty( "M2_HOME" );
233         }
234 
235         if ( defaultMavenHome == null )
236         {
237             File f = new File( System.getProperty( "user.home" ), "m2" );
238             if ( new File( f, "bin/mvn" ).isFile() )
239             {
240                 defaultMavenHome = f.getAbsolutePath();
241             }
242         }
243         return defaultMavenHome;
244     }
245 
246     public void setLocalRepo( String localRepo )
247     {
248         this.localRepo = localRepo;
249     }
250 
251     public void resetStreams()
252     {
253         if ( !debug )
254         {
255             System.setOut( originalOut );
256 
257             System.setErr( originalErr );
258         }
259     }
260 
261     public void displayStreamBuffers()
262     {
263         String out = outStream.toString();
264 
265         if ( out != null && out.trim().length() > 0 )
266         {
267             System.out.println( "----- Standard Out -----" );
268 
269             System.out.println( out );
270         }
271 
272         String err = errStream.toString();
273 
274         if ( err != null && err.trim().length() > 0 )
275         {
276             System.err.println( "----- Standard Error -----" );
277 
278             System.err.println( err );
279         }
280     }
281 
282     // ----------------------------------------------------------------------
283     //
284     // ----------------------------------------------------------------------
285 
286     public void verify( boolean chokeOnErrorOutput )
287         throws VerificationException
288     {
289         List<String> lines = loadFile( getBasedir(), "expected-results.txt", false );
290 
291         for ( String line : lines )
292         {
293             verifyExpectedResult( line );
294         }
295 
296         if ( chokeOnErrorOutput )
297         {
298             verifyErrorFreeLog();
299         }
300     }
301 
302     public void verifyErrorFreeLog()
303         throws VerificationException
304     {
305         List<String> lines = loadFile( getBasedir(), getLogFileName(), false );
306 
307         for ( String line : lines )
308         {
309             // A hack to keep stupid velocity resource loader errors from triggering failure
310             if ( stripAnsi( line ).contains( "[ERROR]" ) && !isVelocityError( line ) )
311             {
312                 throw new VerificationException( "Error in execution: " + line );
313             }
314         }
315     }
316 
317     /**
318      * Checks whether the specified line is just an error message from Velocity. Especially old versions of Doxia employ
319      * a very noisy Velocity instance.
320      *
321      * @param line The log line to check, must not be <code>null</code>.
322      * @return <code>true</code> if the line appears to be a Velocity error, <code>false</code> otherwise.
323      */
324     private static boolean isVelocityError( String line )
325     {
326         return line.contains( "VM_global_library.vm" ) || line.contains( "VM #" ) && line.contains( "macro" );
327     }
328 
329     /**
330      * Throws an exception if the text is not present in the log.
331      *
332      * @param text the text to assert present
333      * @throws VerificationException if text is not found in log
334      */
335     public void verifyTextInLog( String text )
336         throws VerificationException
337     {
338         List<String> lines = loadFile( getBasedir(), getLogFileName(), false );
339 
340         boolean result = false;
341         for ( String line : lines )
342         {
343             if ( stripAnsi( line ).contains( text ) )
344             {
345                 result = true;
346                 break;
347             }
348         }
349         if ( !result )
350         {
351             throw new VerificationException( "Text not found in log: " + text );
352         }
353     }
354 
355     public static String stripAnsi( String msg )
356     {
357         return msg.replaceAll( "\u001B\\[[;\\d]*[ -/]*[@-~]", "" );
358     }
359 
360     public Properties loadProperties( String filename )
361         throws VerificationException
362     {
363         Properties properties = new Properties();
364 
365         File propertiesFile = new File( getBasedir(), filename );
366         try ( FileInputStream fis = new FileInputStream( propertiesFile ) ) 
367         {
368             properties.load( fis );
369         }
370         catch ( IOException e )
371         {
372             throw new VerificationException( "Error reading properties file", e );
373         }
374 
375         return properties;
376     }
377 
378     /**
379      * Loads the (non-empty) lines of the specified text file.
380      *
381      * @param filename The path to the text file to load, relative to the base directory, must not be <code>null</code>.
382      * @param encoding The character encoding of the file, may be <code>null</code> or empty to use the platform default
383      *                 encoding.
384      * @return The list of (non-empty) lines from the text file, can be empty but never <code>null</code>.
385      * @throws IOException If the file could not be loaded.
386      * @since 1.2
387      */
388     public List<String> loadLines( String filename, String encoding )
389         throws IOException
390     {
391         List<String> lines = new ArrayList<String>();
392 
393         try ( BufferedReader reader = getReader( filename, encoding ) )
394         {
395             String line;
396             while ( ( line = reader.readLine() ) != null )
397             {
398                 if ( line.length() > 0 )
399                 {
400                     lines.add( line );
401                 }
402             }
403         }
404 
405         return lines;
406     }
407 
408     private BufferedReader getReader( String filename, String encoding ) throws IOException
409     {
410         File file = new File( getBasedir(), filename );
411 
412         if ( StringUtils.isNotEmpty( encoding ) )
413         {
414             return new BufferedReader( new InputStreamReader( new FileInputStream( file ), encoding ) );
415         }
416         else
417         {
418             return new BufferedReader( new FileReader( file ) );
419         }
420     }
421 
422     public List<String> loadFile( String basedir, String filename, boolean hasCommand )
423         throws VerificationException
424     {
425         return loadFile( new File( basedir, filename ), hasCommand );
426     }
427 
428     public List<String> loadFile( File file, boolean hasCommand )
429         throws VerificationException
430     {
431         List<String> lines = new ArrayList<String>();
432 
433         if ( file.exists() )
434         {
435             try ( BufferedReader reader = new BufferedReader( new FileReader( file ) ) )
436             {
437                 String line = reader.readLine();
438 
439                 while ( line != null )
440                 {
441                     line = line.trim();
442 
443                     if ( !line.startsWith( "#" ) && line.length() != 0 )
444                     {
445                         lines.addAll( replaceArtifacts( line, hasCommand ) );
446                     }
447                     line = reader.readLine();
448                 }
449             }
450             catch ( FileNotFoundException e )
451             {
452                 throw new VerificationException( e );
453             }
454             catch ( IOException e )
455             {
456                 throw new VerificationException( e );
457             }
458         }
459 
460         return lines;
461     }
462 
463     private static final String MARKER = "${artifact:";
464 
465     private List<String> replaceArtifacts( String line, boolean hasCommand )
466     {
467         int index = line.indexOf( MARKER );
468         if ( index >= 0 )
469         {
470             String newLine = line.substring( 0, index );
471             index = line.indexOf( "}", index );
472             if ( index < 0 )
473             {
474                 throw new IllegalArgumentException( "line does not contain ending artifact marker: '" + line + "'" );
475             }
476             String artifact = line.substring( newLine.length() + MARKER.length(), index );
477 
478             newLine += getArtifactPath( artifact );
479             newLine += line.substring( index + 1 );
480 
481             List<String> l = new ArrayList<String>();
482             l.add( newLine );
483 
484             int endIndex = newLine.lastIndexOf( '/' );
485 
486             String command = null;
487             String filespec;
488             if ( hasCommand )
489             {
490                 int startIndex = newLine.indexOf( ' ' );
491 
492                 command = newLine.substring( 0, startIndex );
493 
494                 filespec = newLine.substring( startIndex + 1, endIndex );
495             }
496             else
497             {
498                 filespec = newLine;
499             }
500 
501             File dir = new File( filespec );
502             addMetadataToList( dir, hasCommand, l, command );
503             addMetadataToList( dir.getParentFile(), hasCommand, l, command );
504 
505             return l;
506         }
507         else
508         {
509             return Collections.singletonList( line );
510         }
511     }
512 
513     private static void addMetadataToList( File dir, boolean hasCommand, List<String> l, String command )
514     {
515         if ( dir.exists() && dir.isDirectory() )
516         {
517             String[] files = dir.list( new FilenameFilter()
518             {
519                 public boolean accept( File dir, String name )
520                 {
521                     return name.startsWith( "maven-metadata" ) && name.endsWith( ".xml" );
522 
523                 }
524             } );
525 
526             for ( String file : files )
527             {
528                 if ( hasCommand )
529                 {
530                     l.add( command + " " + new File( dir, file ).getPath() );
531                 }
532                 else
533                 {
534                     l.add( new File( dir, file ).getPath() );
535                 }
536             }
537         }
538     }
539 
540     private String getArtifactPath( String artifact )
541     {
542         StringTokenizer tok = new StringTokenizer( artifact, ":" );
543         if ( tok.countTokens() != 4 )
544         {
545             throw new IllegalArgumentException( "Artifact must have 4 tokens: '" + artifact + "'" );
546         }
547 
548         String[] a = new String[4];
549         for ( int i = 0; i < 4; i++ )
550         {
551             a[i] = tok.nextToken();
552         }
553 
554         String groupId = a[0];
555         String artifactId = a[1];
556         String version = a[2];
557         String ext = a[3];
558         return getArtifactPath( groupId, artifactId, version, ext );
559     }
560 
561     public String getArtifactPath( String groupId, String artifactId, String version, String ext )
562     {
563         return getArtifactPath( groupId, artifactId, version, ext, null );
564     }
565 
566     /**
567      * Returns the absolute path to the artifact denoted by groupId, artifactId, version, extension and classifier.
568      *
569      * @param gid        The groupId, must not be null.
570      * @param aid        The artifactId, must not be null.
571      * @param version    The version, must not be null.
572      * @param ext        The extension, must not be null.
573      * @param classifier The classifier, may be null to be omitted.
574      * @return the absolute path to the artifact denoted by groupId, artifactId, version, extension and classifier,
575      *         never null.
576      */
577     public String getArtifactPath( String gid, String aid, String version, String ext, String classifier )
578     {
579         if ( classifier != null && classifier.length() == 0 )
580         {
581             classifier = null;
582         }
583         if ( "maven-plugin".equals( ext ) )
584         {
585             ext = "jar";
586         }
587         if ( "coreit-artifact".equals( ext ) )
588         {
589             ext = "jar";
590             classifier = "it";
591         }
592         if ( "test-jar".equals( ext ) )
593         {
594             ext = "jar";
595             classifier = "tests";
596         }
597 
598         String repositoryPath;
599         if ( "legacy".equals( localRepoLayout ) )
600         {
601             repositoryPath = gid + "/" + ext + "s/" + aid + "-" + version + "." + ext;
602         }
603         else if ( "default".equals( localRepoLayout ) )
604         {
605             repositoryPath = gid.replace( '.', '/' );
606             repositoryPath = repositoryPath + "/" + aid + "/" + version;
607             repositoryPath = repositoryPath + "/" + aid + "-" + version;
608             if ( classifier != null )
609             {
610                 repositoryPath = repositoryPath + "-" + classifier;
611             }
612             repositoryPath = repositoryPath + "." + ext;
613         }
614         else
615         {
616             throw new IllegalStateException( "Unknown layout: " + localRepoLayout );
617         }
618 
619         return localRepo + "/" + repositoryPath;
620     }
621 
622     public List<String> getArtifactFileNameList( String org, String name, String version, String ext )
623     {
624         List<String> files = new ArrayList<String>();
625         String artifactPath = getArtifactPath( org, name, version, ext );
626         File dir = new File( artifactPath );
627         files.add( artifactPath );
628         addMetadataToList( dir, false, files, null );
629         addMetadataToList( dir.getParentFile(), false, files, null );
630         return files;
631     }
632 
633     /**
634      * Gets the path to the local artifact metadata. Note that the method does not check whether the returned path
635      * actually points to existing metadata.
636      *
637      * @param gid     The group id, must not be <code>null</code>.
638      * @param aid     The artifact id, must not be <code>null</code>.
639      * @param version The artifact version, may be <code>null</code>.
640      * @return The (absolute) path to the local artifact metadata, never <code>null</code>.
641      */
642     public String getArtifactMetadataPath( String gid, String aid, String version )
643     {
644         return getArtifactMetadataPath( gid, aid, version, "maven-metadata-local.xml" );
645     }
646 
647     /**
648      * Gets the path to a file in the local artifact directory. Note that the method does not check whether the returned
649      * path actually points to an existing file.
650      *
651      * @param gid      The group id, must not be <code>null</code>.
652      * @param aid      The artifact id, may be <code>null</code>.
653      * @param version  The artifact version, may be <code>null</code>.
654      * @param filename The filename to use, must not be <code>null</code>.
655      * @return The (absolute) path to the local artifact metadata, never <code>null</code>.
656      */
657     public String getArtifactMetadataPath( String gid, String aid, String version, String filename )
658     {
659         StringBuilder buffer = new StringBuilder( 256 );
660 
661         buffer.append( localRepo );
662         buffer.append( '/' );
663 
664         if ( "default".equals( localRepoLayout ) )
665         {
666             buffer.append( gid.replace( '.', '/' ) );
667             buffer.append( '/' );
668 
669             if ( aid != null )
670             {
671                 buffer.append( aid );
672                 buffer.append( '/' );
673 
674                 if ( version != null )
675                 {
676                     buffer.append( version );
677                     buffer.append( '/' );
678                 }
679             }
680 
681             buffer.append( filename );
682         }
683         else
684         {
685             throw new IllegalStateException( "Unsupported repository layout: " + localRepoLayout );
686         }
687 
688         return buffer.toString();
689     }
690 
691     /**
692      * Gets the path to the local artifact metadata. Note that the method does not check whether the returned path
693      * actually points to existing metadata.
694      *
695      * @param gid The group id, must not be <code>null</code>.
696      * @param aid The artifact id, must not be <code>null</code>.
697      * @return The (absolute) path to the local artifact metadata, never <code>null</code>.
698      */
699     public String getArtifactMetadataPath( String gid, String aid )
700     {
701         return getArtifactMetadataPath( gid, aid, null );
702     }
703 
704     public void executeHook( String filename )
705         throws VerificationException
706     {
707         try
708         {
709             File f = new File( getBasedir(), filename );
710 
711             if ( !f.exists() )
712             {
713                 return;
714             }
715 
716             List<String> lines = loadFile( f, true );
717 
718             for ( String line1 : lines )
719             {
720                 String line = resolveCommandLineArg( line1 );
721 
722                 executeCommand( line );
723             }
724         }
725         catch ( VerificationException e )
726         {
727             throw e;
728         }
729         catch ( Exception e )
730         {
731             throw new VerificationException( e );
732         }
733     }
734 
735     private void executeCommand( String line )
736         throws VerificationException
737     {
738         int index = line.indexOf( " " );
739 
740         String cmd;
741 
742         String args = null;
743 
744         if ( index >= 0 )
745         {
746             cmd = line.substring( 0, index );
747 
748             args = line.substring( index + 1 );
749         }
750         else
751         {
752             cmd = line;
753         }
754 
755         if ( "rm".equals( cmd ) )
756         {
757             System.out.println( "Removing file: " + args );
758 
759             File f = new File( args );
760 
761             if ( f.exists() && !f.delete() )
762             {
763                 throw new VerificationException( "Error removing file - delete failed" );
764             }
765         }
766         else if ( "rmdir".equals( cmd ) )
767         {
768             System.out.println( "Removing directory: " + args );
769 
770             try
771             {
772                 File f = new File( args );
773 
774                 FileUtils.deleteDirectory( f );
775             }
776             catch ( IOException e )
777             {
778                 throw new VerificationException( "Error removing directory - delete failed" );
779             }
780         }
781         else if ( "svn".equals( cmd ) )
782         {
783             launchSubversion( line, getBasedir() );
784         }
785         else
786         {
787             throw new VerificationException( "unknown command: " + cmd );
788         }
789     }
790 
791     public static void launchSubversion( String line, String basedir )
792         throws VerificationException
793     {
794         try
795         {
796             Commandline cli = new Commandline( line );
797 
798             cli.setWorkingDirectory( basedir );
799 
800             try ( Writer logWriter = new FileWriter( new File( basedir, LOG_FILENAME ) ) ) 
801             {
802                 StreamConsumer out = new WriterStreamConsumer( logWriter );
803 
804                 StreamConsumer err = new WriterStreamConsumer( logWriter );
805 
806                 System.out.println( "Command: " + CommandLineUtils.toString( cli.getCommandline() ) );
807 
808                 int ret = CommandLineUtils.executeCommandLine( cli, out, err );
809 
810                 if ( ret > 0 )
811                 {
812                     System.err.println( "Exit code: " + ret );
813 
814                     throw new VerificationException();
815                 }
816             }
817         }
818         catch ( CommandLineException e )
819         {
820             throw new VerificationException( e );
821         }
822         catch ( IOException e )
823         {
824             throw new VerificationException( e );
825         }
826     }
827 
828     private static String retrieveLocalRepo( String settingsXmlPath )
829         throws VerificationException
830     {
831         UserModelReader userModelReader = new UserModelReader();
832 
833         String userHome = System.getProperty( "user.home" );
834 
835         File userXml;
836 
837         String repo = null;
838 
839         if ( settingsXmlPath != null )
840         {
841             System.out.println( "Using settings from " + settingsXmlPath );
842             userXml = new File( settingsXmlPath );
843         }
844         else
845         {
846             userXml = new File( userHome, ".m2/settings.xml" );
847         }
848 
849         if ( userXml.exists() )
850         {
851             userModelReader.parse( userXml );
852 
853             String localRepository = userModelReader.getLocalRepository();
854             if ( localRepository != null )
855             {
856                 repo = new File( localRepository ).getAbsolutePath();
857             }
858         }
859 
860         return repo;
861     }
862 
863     public void deleteArtifact( String org, String name, String version, String ext )
864         throws IOException
865     {
866         List<String> files = getArtifactFileNameList( org, name, version, ext );
867         for ( String fileName : files )
868         {
869             FileUtils.forceDelete( new File( fileName ) );
870         }
871     }
872 
873     /**
874      * Deletes all artifacts in the specified group id from the local repository.
875      *
876      * @param gid The group id whose artifacts should be deleted, must not be <code>null</code>.
877      * @throws IOException If the artifacts could not be deleted.
878      * @since 1.2
879      */
880     public void deleteArtifacts( String gid )
881         throws IOException
882     {
883         String path;
884         if ( "default".equals( localRepoLayout ) )
885         {
886             path = gid.replace( '.', '/' );
887         }
888         else if ( "legacy".equals( localRepoLayout ) )
889         {
890             path = gid;
891         }
892         else
893         {
894             throw new IllegalStateException( "Unsupported repository layout: " + localRepoLayout );
895         }
896 
897         FileUtils.deleteDirectory( new File( localRepo, path ) );
898     }
899 
900     /**
901      * Deletes all artifacts in the specified g:a:v from the local repository.
902      *
903      * @param gid     The group id whose artifacts should be deleted, must not be <code>null</code>.
904      * @param aid     The artifact id whose artifacts should be deleted, must not be <code>null</code>.
905      * @param version The (base) version whose artifacts should be deleted, must not be <code>null</code>.
906      * @throws IOException If the artifacts could not be deleted.
907      * @since 1.3
908      */
909     public void deleteArtifacts( String gid, String aid, String version )
910         throws IOException
911     {
912         String path;
913         if ( "default".equals( localRepoLayout ) )
914         {
915             path = gid.replace( '.', '/' ) + '/' + aid + '/' + version;
916         }
917         else
918         {
919             throw new IllegalStateException( "Unsupported repository layout: " + localRepoLayout );
920         }
921 
922         FileUtils.deleteDirectory( new File( localRepo, path ) );
923     }
924 
925     /**
926      * Deletes the specified directory.
927      *
928      * @param path The path to the directory to delete, relative to the base directory, must not be <code>null</code>.
929      * @throws IOException If the directory could not be deleted.
930      * @since 1.2
931      */
932     public void deleteDirectory( String path )
933         throws IOException
934     {
935         FileUtils.deleteDirectory( new File( getBasedir(), path ) );
936     }
937 
938     /**
939      * Writes a text file with the specified contents. The contents will be encoded using UTF-8.
940      *
941      * @param path     The path to the file, relative to the base directory, must not be <code>null</code>.
942      * @param contents The contents to write, must not be <code>null</code>.
943      * @throws IOException If the file could not be written.
944      * @since 1.2
945      */
946     public void writeFile( String path, String contents )
947         throws IOException
948     {
949         FileUtils.fileWrite( new File( getBasedir(), path ).getAbsolutePath(), "UTF-8", contents );
950     }
951 
952     /**
953      * Filters a text file by replacing some user-defined tokens.
954      *
955      * @param srcPath          The path to the input file, relative to the base directory, must not be
956      *                         <code>null</code>.
957      * @param dstPath          The path to the output file, relative to the base directory and possibly equal to the
958      *                         input file, must not be <code>null</code>.
959      * @param fileEncoding     The file encoding to use, may be <code>null</code> or empty to use the platform's default
960      *                         encoding.
961      * @param filterProperties The mapping from tokens to replacement values, must not be <code>null</code>.
962      * @return The path to the filtered output file, never <code>null</code>.
963      * @throws IOException If the file could not be filtered.
964      * @since 1.2
965      */
966     public File filterFile( String srcPath, String dstPath, String fileEncoding, Map<String, String> filterProperties )
967         throws IOException
968     {
969         File srcFile = new File( getBasedir(), srcPath );
970         String data = FileUtils.fileRead( srcFile, fileEncoding );
971 
972         for ( String token : filterProperties.keySet() )
973         {
974             String value = String.valueOf( filterProperties.get( token ) );
975             data = StringUtils.replace( data, token, value );
976         }
977 
978         File dstFile = new File( getBasedir(), dstPath );
979         //noinspection ResultOfMethodCallIgnored
980         dstFile.getParentFile().mkdirs();
981         FileUtils.fileWrite( dstFile.getPath(), fileEncoding, data );
982 
983         return dstFile;
984     }
985 
986     /**
987      * There are 226 references to this method in Maven core ITs. In most (all?) cases it is used together with
988      * {@link #newDefaultFilterProperties()}. Need to remove both methods and update all clients eventually/
989      * 
990      * @param srcPath          The path to the input file, relative to the base directory, must not be
991      *                         <code>null</code>.
992      * @param dstPath          The path to the output file, relative to the base directory and possibly equal to the
993      *                         input file, must not be <code>null</code>.
994      * @param fileEncoding     The file encoding to use, may be <code>null</code> or empty to use the platform's default
995      *                         encoding.
996      * @param filterProperties The mapping from tokens to replacement values, must not be <code>null</code>.
997      * @return The path to the filtered output file, never <code>null</code>.
998      * @throws IOException If the file could not be filtered.
999      * @deprecated use {@link #filterFile(String, String, String, Map)}
1000      */
1001     @Deprecated
1002     @SuppressWarnings( { "rawtypes", "unchecked" } )
1003     public File filterFile( String srcPath, String dstPath, String fileEncoding, Properties filterProperties )
1004         throws IOException
1005     {
1006         return filterFile( srcPath, dstPath, fileEncoding, (Map) filterProperties );
1007     }
1008 
1009     /**
1010      * Gets a new copy of the default filter properties. These default filter properties map the tokens "@basedir@" and
1011      * "@baseurl@" to the test's base directory and its base <code>file:</code> URL, respectively.
1012      *
1013      * @return The (modifiable) map with the default filter properties, never <code>null</code>.
1014      * @since 1.2
1015      */
1016     public Properties newDefaultFilterProperties()
1017     {
1018         Properties filterProperties = new Properties();
1019 
1020         String basedir = new File( getBasedir() ).getAbsolutePath();
1021         filterProperties.put( "@basedir@", basedir );
1022 
1023         /*
1024          * NOTE: Maven fails to properly handle percent-encoded "file:" URLs (WAGON-111) so don't use File.toURI() here
1025          * and just do it the simple way.
1026          */
1027         String baseurl = basedir;
1028         if ( !baseurl.startsWith( "/" ) )
1029         {
1030             baseurl = '/' + baseurl;
1031         }
1032         baseurl = "file://" + baseurl.replace( '\\', '/' );
1033         filterProperties.put( "@baseurl@", baseurl );
1034 
1035         return filterProperties;
1036     }
1037 
1038     /**
1039      * Verifies that the given file exists.
1040      * 
1041      * @param file the path of the file to check
1042      * @throws VerificationException in case the given file does not exist
1043      */
1044     public void verifyFilePresent( String file ) throws VerificationException
1045     {
1046         verifyFilePresence( file, true );
1047     }
1048 
1049     /**
1050      * 
1051      * @param file the path of the file to check
1052      * @deprecated Use {@link #verifyFilePresent(String)} instead.
1053      */
1054     @Deprecated
1055     public void assertFilePresent( String file )
1056     {
1057         try
1058         {
1059             verifyFilePresent( file );
1060         }
1061         catch ( VerificationException e )
1062         {
1063             Assert.fail( e.getMessage() );
1064         }
1065     }
1066 
1067     /**
1068      * Verifies the given file's content matches an regular expression. 
1069      * Note this method also checks that the file exists and is readable.
1070      *
1071      * @param file the path of the file to check
1072      * @param regex a regular expression
1073      * @throws VerificationException in case the file was not found or its content does not match the given pattern
1074      * @see Pattern
1075      */
1076     public void verifyFileContentMatches( String file, String regex ) throws VerificationException
1077     {
1078         verifyFilePresent( file );
1079         try
1080         {
1081             String content = FileUtils.fileRead( file );
1082             if ( !Pattern.matches( regex, content ) )
1083             {
1084                 throw new VerificationException( "Content of " + file + " does not match " + regex );
1085             }
1086         }
1087         catch ( IOException e )
1088         {
1089             throw new VerificationException( "Could not read from " + file + ": " + e.getMessage(), e );
1090         }
1091     }
1092 
1093     /**
1094      * Check the given file's content matches a regular expression. Note this method also checks that the file exists
1095      * and is readable.
1096      *
1097      * @param file the path of the file to check
1098      * @param regex a regular expression that the file's contents should match
1099      * @see Pattern
1100      * @deprecated Use {@link #verifyFileContentMatches(String, String)} instead.
1101      */
1102     @Deprecated
1103     public void assertFileMatches( String file, String regex )
1104     {
1105         try
1106         {
1107             verifyFileContentMatches( file, regex );
1108         }
1109         catch ( VerificationException e )
1110         {
1111             Assert.fail( e.getMessage() );
1112         }
1113     }
1114 
1115     /**
1116      * Verifies that the given file does not exist.
1117      * 
1118      * @param file the path of the file to check
1119      * @throws VerificationException if the given file exists
1120      */
1121     public void verifyFileNotPresent( String file ) throws VerificationException
1122     {
1123         verifyFilePresence( file, false );
1124     }
1125 
1126     /**
1127      * 
1128      * @param file the path of the file to check
1129      * @deprecated Use {@link #verifyFileNotPresent(String)} instead.
1130      */
1131     @Deprecated
1132     public void assertFileNotPresent( String file )
1133     {
1134         try
1135         {
1136             verifyFileNotPresent( file );
1137         }
1138         catch ( VerificationException e )
1139         {
1140             Assert.fail( e.getMessage() );
1141         }
1142     }
1143 
1144     private void verifyArtifactPresence( boolean wanted, String groupId, String artifactId, String version, String ext )
1145                     throws VerificationException
1146     {
1147         List<String> files = getArtifactFileNameList( groupId, artifactId, version, ext );
1148         for ( String fileName : files )
1149         {
1150             verifyFilePresence( fileName, wanted );
1151         }
1152     }
1153 
1154     /**
1155      * Verifies that the artifact given through its Maven coordinates exists.
1156      * 
1157      * @param groupId the groupId of the artifact (must not be null)
1158      * @param artifactId the artifactId of the artifact (must not be null)
1159      * @param version the version of the artifact (must not be null)
1160      * @param ext the extension of the artifact (must not be null)
1161      * @throws VerificationException if the given artifact does not exist
1162      */
1163     public void verifyArtifactPresent( String groupId, String artifactId, String version, String ext )
1164                     throws VerificationException
1165     {
1166         verifyArtifactPresence( true, groupId, artifactId, version, ext );
1167     }
1168 
1169     /**
1170      * Verifies that the artifact given through its Maven coordinates does not exist.
1171      * 
1172      * @param groupId the groupId of the artifact (must not be null)
1173      * @param artifactId the artifactId of the artifact (must not be null)
1174      * @param version the version of the artifact (must not be null)
1175      * @param ext the extension of the artifact (must not be null)
1176      * @throws VerificationException if the given artifact exists
1177      */
1178     public void verifyArtifactNotPresent( String groupId, String artifactId, String version, String ext )
1179                     throws VerificationException
1180     {
1181         verifyArtifactPresence( false, groupId, artifactId, version, ext );
1182     }
1183 
1184     private void assertArtifactPresence( boolean wanted, String org, String name, String version, String ext )
1185     {
1186         try
1187         {
1188             verifyArtifactPresence( wanted, org, name, version, ext );
1189         }
1190         catch ( VerificationException e )
1191         {
1192             Assert.fail( e.getMessage() );
1193         }
1194     }
1195 
1196     /**
1197      * @deprecated Use {@link #verifyArtifactPresent(String, String, String, String)} instead.
1198      */
1199     @Deprecated
1200     public void assertArtifactPresent( String org, String name, String version, String ext )
1201     {
1202         assertArtifactPresence( true, org, name, version, ext );
1203     }
1204 
1205     /**
1206      * @deprecated Use {@link #verifyArtifactNotPresent(String, String, String, String)} instead.
1207      */
1208     @Deprecated
1209     public void assertArtifactNotPresent( String org, String name, String version, String ext )
1210     {
1211         assertArtifactPresence( false, org, name, version, ext );
1212     }
1213 
1214     private void verifyExpectedResult( String line )
1215         throws VerificationException
1216     {
1217         boolean wanted = true;
1218         if ( line.startsWith( "!" ) )
1219         {
1220             line = line.substring( 1 );
1221             wanted = false;
1222         }
1223 
1224         verifyFilePresence( line, wanted );
1225     }
1226 
1227     private void verifyFilePresence( String filePath, boolean wanted )
1228         throws VerificationException
1229     {
1230         if ( filePath.indexOf( "!/" ) > 0 )
1231         {
1232             String urlString = "jar:file:" + getBasedir() + "/" + filePath;
1233 
1234             InputStream is = null;
1235             try
1236             {
1237                 URL url = new URL( urlString );
1238 
1239                 is = url.openStream();
1240 
1241                 if ( is == null )
1242                 {
1243                     if ( wanted )
1244                     {
1245                         throw new VerificationException( "Expected JAR resource was not found: " + filePath );
1246                     }
1247                 }
1248                 else
1249                 {
1250                     if ( !wanted )
1251                     {
1252                         throw new VerificationException( "Unwanted JAR resource was found: " + filePath );
1253                     }
1254                 }
1255             }
1256             catch ( MalformedURLException e )
1257             {
1258                 throw new VerificationException( "Error looking for JAR resource", e );
1259             }
1260             catch ( IOException e )
1261             {
1262                 if ( wanted )
1263                 {
1264                     throw new VerificationException( "Error looking for JAR resource: " + filePath );
1265                 }
1266             }
1267             finally
1268             {
1269                 if ( is != null )
1270                 {
1271                     try
1272                     {
1273                         is.close();
1274                     }
1275                     catch ( IOException e )
1276                     {
1277                         System.err.println( "WARN: error closing stream: " + e );
1278                     }
1279                 }
1280             }
1281         }
1282         else
1283         {
1284             File expectedFile = new File( filePath );
1285 
1286             // NOTE: On Windows, a path with a leading (back-)slash is relative to the current drive
1287             if ( !expectedFile.isAbsolute() && !expectedFile.getPath().startsWith( File.separator ) )
1288             {
1289                 expectedFile = new File( getBasedir(), filePath );
1290             }
1291 
1292             if ( filePath.indexOf( '*' ) > -1 )
1293             {
1294                 File parent = expectedFile.getParentFile();
1295 
1296                 if ( !parent.exists() )
1297                 {
1298                     if ( wanted )
1299                     {
1300                         throw new VerificationException(
1301                             "Expected file pattern was not found: " + expectedFile.getPath() );
1302                     }
1303                 }
1304                 else
1305                 {
1306                     String shortNamePattern = expectedFile.getName().replaceAll( "\\*", ".*" );
1307 
1308                     String[] candidates = parent.list();
1309 
1310                     boolean found = false;
1311 
1312                     if ( candidates != null )
1313                     {
1314                         for ( String candidate : candidates )
1315                         {
1316                             if ( candidate.matches( shortNamePattern ) )
1317                             {
1318                                 found = true;
1319                                 break;
1320                             }
1321                         }
1322                     }
1323 
1324                     if ( !found && wanted )
1325                     {
1326                         throw new VerificationException(
1327                             "Expected file pattern was not found: " + expectedFile.getPath() );
1328                     }
1329                     else if ( found && !wanted )
1330                     {
1331                         throw new VerificationException( "Unwanted file pattern was found: " + expectedFile.getPath() );
1332                     }
1333                 }
1334             }
1335             else
1336             {
1337                 if ( !expectedFile.exists() )
1338                 {
1339                     if ( wanted )
1340                     {
1341                         throw new VerificationException( "Expected file was not found: " + expectedFile.getPath() );
1342                     }
1343                 }
1344                 else
1345                 {
1346                     if ( !wanted )
1347                     {
1348                         throw new VerificationException( "Unwanted file was found: " + expectedFile.getPath() );
1349                     }
1350                 }
1351             }
1352         }
1353     }
1354 
1355     // ----------------------------------------------------------------------
1356     //
1357     // ----------------------------------------------------------------------
1358 
1359     public void executeGoal( String goal )
1360         throws VerificationException
1361     {
1362         executeGoal( goal, environmentVariables );
1363     }
1364 
1365     public void executeGoal( String goal, Map<String, String> envVars )
1366         throws VerificationException
1367     {
1368         executeGoals( Arrays.asList( goal ), envVars );
1369     }
1370 
1371     public void executeGoals( List<String> goals )
1372         throws VerificationException
1373     {
1374         executeGoals( goals, environmentVariables );
1375     }
1376 
1377     public String getExecutable()
1378     {
1379         // Use a strategy for finding the maven executable, John has a simple method like this
1380         // but a little strategy + chain of command would be nicer.
1381 
1382         if ( mavenHome != null )
1383         {
1384             return mavenHome + "/bin/mvn";
1385         }
1386         else
1387         {
1388             File f = new File( System.getProperty( "user.home" ), "m2/bin/mvn" );
1389 
1390             if ( f.exists() )
1391             {
1392                 return f.getAbsolutePath();
1393             }
1394             else
1395             {
1396                 return "mvn";
1397             }
1398         }
1399     }
1400 
1401     public void executeGoals( List<String> goals, Map<String, String> envVars )
1402         throws VerificationException
1403     {
1404         List<String> allGoals = new ArrayList<String>();
1405 
1406         if ( autoclean )
1407         {
1408             /*
1409              * NOTE: Neither test lifecycle binding nor prefix resolution here but call the goal directly.
1410              */
1411             allGoals.add( "org.apache.maven.plugins:maven-clean-plugin:clean" );
1412         }
1413 
1414         allGoals.addAll( goals );
1415 
1416         List<String> args = new ArrayList<String>();
1417 
1418         int ret;
1419 
1420         File logFile = new File( getBasedir(), getLogFileName() );
1421 
1422         for ( Object cliOption : cliOptions )
1423         {
1424             String key = String.valueOf( cliOption );
1425 
1426             String resolvedArg = resolveCommandLineArg( key );
1427 
1428             try
1429             {
1430                 args.addAll( Arrays.asList( CommandLineUtils.translateCommandline( resolvedArg ) ) );
1431             }
1432             catch ( Exception e )
1433             {
1434                 e.printStackTrace();
1435             }
1436         }
1437 
1438         Collections.addAll( args, defaultCliOptions );
1439 
1440         if ( this.mavenDebug )
1441         {
1442             args.add( "--debug" );
1443         }
1444 
1445         /*
1446          * NOTE: Unless explicitly requested by the caller, the forked builds should use the current local
1447          * repository. Otherwise, the forked builds would in principle leave the sandbox environment which has been
1448          * setup for the current build. In particular, using "maven.repo.local" will make sure the forked builds use
1449          * the same local repo as the parent build even if a custom user settings is provided.
1450          */
1451         boolean useMavenRepoLocal = Boolean.valueOf( verifierProperties.getProperty( "use.mavenRepoLocal", "true" ) );
1452 
1453         if ( useMavenRepoLocal )
1454         {
1455             args.add( "-Dmaven.repo.local=" + localRepo );
1456         }
1457 
1458         args.addAll( allGoals );
1459 
1460         try
1461         {
1462             String[] cliArgs = args.toArray( new String[args.size()] );
1463 
1464             MavenLauncher launcher = getMavenLauncher( envVars );
1465 
1466             ret = launcher.run( cliArgs, systemProperties, getBasedir(), logFile );
1467         }
1468         catch ( LauncherException e )
1469         {
1470             throw new VerificationException( "Failed to execute Maven: " + e.getMessage(), e );
1471         }
1472         catch ( IOException e )
1473         {
1474             throw new VerificationException( e );
1475         }
1476 
1477         if ( ret > 0 )
1478         {
1479             System.err.println( "Exit code: " + ret );
1480 
1481             throw new VerificationException(
1482                 "Exit code was non-zero: " + ret + "; command line and log = \n" + new File( mavenHome,
1483                                                                                              "bin/mvn" ) + " "
1484                     + StringUtils.join( args.iterator(), " " ) + "\n" + getLogContents( logFile ) );
1485         }
1486     }
1487 
1488     private MavenLauncher getMavenLauncher( Map<String, String> envVars )
1489         throws LauncherException
1490     {
1491         boolean fork;
1492         if ( useWrapper )
1493         {
1494             fork = true;
1495         }
1496         else if ( forkJvm != null )
1497         {
1498             fork = forkJvm;
1499         }
1500         else if ( ( envVars.isEmpty() && "auto".equalsIgnoreCase( forkMode ) )
1501             || "embedded".equalsIgnoreCase( forkMode ) )
1502         {
1503             fork = false;
1504 
1505             try
1506             {
1507                 initEmbeddedLauncher();
1508             }
1509             catch ( Exception e )
1510             {
1511                 fork = true;
1512             }
1513         }
1514         else
1515         {
1516             fork = true;
1517         }
1518 
1519         if ( !fork )
1520         {
1521             if ( !envVars.isEmpty() )
1522             {
1523                 throw new LauncherException( "Environment variables are not supported in embedded runtime" );
1524             }
1525 
1526             initEmbeddedLauncher();
1527 
1528             return embeddedLauncher;
1529         }
1530         else
1531         {
1532             return new ForkedLauncher( mavenHome, envVars, debugJvm, useWrapper );
1533         }
1534     }
1535 
1536     private void initEmbeddedLauncher()
1537         throws LauncherException
1538     {
1539         if ( embeddedLauncher == null )
1540         {
1541             if ( StringUtils.isEmpty( mavenHome ) )
1542             {
1543                 embeddedLauncher = Embedded3xLauncher.createFromClasspath();
1544             }
1545             else
1546             {
1547                 String defaultClasspath = System.getProperty( "maven.bootclasspath" );
1548                 String defaultClassworldConf = System.getProperty( "classworlds.conf" );
1549                 embeddedLauncher = Embedded3xLauncher.createFromMavenHome( mavenHome, defaultClassworldConf, 
1550                         parseClasspath( defaultClasspath ) );
1551             }
1552         }
1553     }
1554 
1555     private static List<URL> parseClasspath( String classpath )
1556         throws LauncherException
1557     {
1558         if ( classpath == null )
1559         {
1560             return null;
1561         }
1562         ArrayList<URL> classpathUrls = new ArrayList<URL>();
1563         StringTokenizer st = new StringTokenizer( classpath, File.pathSeparator );
1564         while ( st.hasMoreTokens() )
1565         {
1566             try
1567             {
1568                 classpathUrls.add( new File( st.nextToken() ).toURI().toURL() );
1569             }
1570             catch ( MalformedURLException e )
1571             {
1572                 throw new LauncherException( "Invalid launcher classpath " + classpath, e );
1573             }
1574         }
1575         return classpathUrls;
1576     }
1577 
1578     public String getMavenVersion()
1579         throws VerificationException
1580     {
1581         try
1582         {
1583             return getMavenLauncher( Collections.<String, String>emptyMap() ).getMavenVersion();
1584         }
1585         catch ( LauncherException e )
1586         {
1587             throw new VerificationException( e );
1588         }
1589         catch ( IOException e )
1590         {
1591             throw new VerificationException( e );
1592         }
1593     }
1594 
1595     private static String getLogContents( File logFile )
1596     {
1597         try
1598         {
1599             return FileUtils.fileRead( logFile );
1600         }
1601         catch ( IOException e )
1602         {
1603             // ignore
1604             return "(Error reading log contents: " + e.getMessage() + ")";
1605         }
1606     }
1607 
1608     private String resolveCommandLineArg( String key )
1609     {
1610         String result = key.replaceAll( "\\$\\{basedir\\}", getBasedir() );
1611         if ( result.contains( "\\\\" ) )
1612         {
1613             result = result.replaceAll( "\\\\", "\\" );
1614         }
1615         result = result.replaceAll( "\\/\\/", "\\/" );
1616 
1617         return result;
1618     }
1619 
1620     private static List<String> discoverIntegrationTests( String directory )
1621         throws VerificationException
1622     {
1623         try
1624         {
1625             ArrayList<String> tests = new ArrayList<String>();
1626 
1627             List<File> subTests = FileUtils.getFiles( new File( directory ), "**/goals.txt", null );
1628 
1629             for ( File testCase : subTests )
1630             {
1631                 tests.add( testCase.getParent() );
1632             }
1633 
1634             return tests;
1635         }
1636         catch ( IOException e )
1637         {
1638             throw new VerificationException( directory + " is not a valid test case container", e );
1639         }
1640     }
1641 
1642     private void displayLogFile()
1643     {
1644         System.out.println( "Log file contents:" );
1645 
1646         try ( BufferedReader reader = 
1647                         new BufferedReader( new FileReader( new File( getBasedir(), getLogFileName() ) ) ) )
1648         {
1649             String line = reader.readLine();
1650             
1651             while ( line != null )
1652             {
1653                 System.out.println( line );
1654                 line = reader.readLine();
1655             }
1656         }
1657         catch ( IOException e )
1658         {
1659             System.err.println( "Error: " + e );
1660         }
1661     }
1662 
1663     // ----------------------------------------------------------------------
1664     //
1665     // ----------------------------------------------------------------------
1666 
1667     public static void main( String args[] )
1668         throws VerificationException
1669     {
1670         String basedir = System.getProperty( "user.dir" );
1671 
1672         List<String> tests = null;
1673 
1674         List<String> argsList = new ArrayList<String>();
1675 
1676         String settingsFile = null;
1677 
1678         // skip options
1679         for ( int i = 0; i < args.length; i++ )
1680         {
1681             if ( args[i].startsWith( "-D" ) )
1682             {
1683                 int index = args[i].indexOf( "=" );
1684                 if ( index >= 0 )
1685                 {
1686                     System.setProperty( args[i].substring( 2, index ), args[i].substring( index + 1 ) );
1687                 }
1688                 else
1689                 {
1690                     System.setProperty( args[i].substring( 2 ), "true" );
1691                 }
1692             }
1693             else if ( "-s".equals( args[i] ) || "--settings".equals( args[i] ) )
1694             {
1695                 if ( i == args.length - 1 )
1696                 {
1697                     // should have been detected before
1698                     throw new IllegalStateException( "missing argument to -s" );
1699                 }
1700                 i += 1;
1701 
1702                 settingsFile = args[i];
1703             }
1704             else if ( args[i].startsWith( "-" ) )
1705             {
1706                 System.out.println( "skipping unrecognised argument: " + args[i] );
1707             }
1708             else
1709             {
1710                 argsList.add( args[i] );
1711             }
1712         }
1713 
1714         if ( argsList.size() == 0 )
1715         {
1716             if ( FileUtils.fileExists( basedir + File.separator + "integration-tests.txt" ) )
1717             {
1718                 try
1719                 {
1720                     tests = FileUtils.loadFile( new File( basedir, "integration-tests.txt" ) );
1721                 }
1722                 catch ( IOException e )
1723                 {
1724                     System.err.println( "Unable to load integration tests file" );
1725 
1726                     System.err.println( e.getMessage() );
1727 
1728                     System.exit( 2 );
1729                 }
1730             }
1731             else
1732             {
1733                 tests = discoverIntegrationTests( "." );
1734             }
1735         }
1736         else
1737         {
1738             tests = new ArrayList<String>( argsList.size() );
1739             NumberFormat fmt = new DecimalFormat( "0000" );
1740             for ( String test : argsList )
1741             {
1742                 if ( test.endsWith( "," ) )
1743                 {
1744                     test = test.substring( 0, test.length() - 1 );
1745                 }
1746 
1747                 if ( StringUtils.isNumeric( test ) )
1748                 {
1749 
1750                     test = "it" + fmt.format( Integer.valueOf( test ) );
1751                     tests.add( test.trim() );
1752                 }
1753                 else if ( "it".startsWith( test ) )
1754                 {
1755                     test = test.trim();
1756                     if ( test.length() > 0 )
1757                     {
1758                         tests.add( test );
1759                     }
1760                 }
1761                 else if ( FileUtils.fileExists( test ) && new File( test ).isDirectory() )
1762                 {
1763                     tests.addAll( discoverIntegrationTests( test ) );
1764                 }
1765                 else
1766                 {
1767                     System.err.println(
1768                         "[WARNING] rejecting " + test + " as an invalid test or test source directory" );
1769                 }
1770             }
1771         }
1772 
1773         if ( tests.size() == 0 )
1774         {
1775             System.out.println( "No tests to run" );
1776         }
1777 
1778         int exitCode = 0;
1779 
1780         List<String> failed = new ArrayList<String>();
1781         for ( String test : tests )
1782         {
1783             System.out.print( test + "... " );
1784 
1785             String dir = basedir + "/" + test;
1786 
1787             if ( !new File( dir, "goals.txt" ).exists() )
1788             {
1789                 System.err.println( "Test " + test + " in " + dir + " does not exist" );
1790 
1791                 System.exit( 2 );
1792             }
1793 
1794             Verifier verifier = new Verifier( dir );
1795             verifier.findLocalRepo( settingsFile );
1796 
1797             System.out.println( "Using default local repository: " + verifier.localRepo );
1798 
1799             try
1800             {
1801                 runIntegrationTest( verifier );
1802             }
1803             catch ( Throwable e )
1804             {
1805                 verifier.resetStreams();
1806 
1807                 System.out.println( "FAILED" );
1808 
1809                 verifier.displayStreamBuffers();
1810 
1811                 System.out.println( ">>>>>> Error Stacktrace:" );
1812                 e.printStackTrace( System.out );
1813                 System.out.println( "<<<<<< Error Stacktrace" );
1814 
1815                 verifier.displayLogFile();
1816 
1817                 exitCode = 1;
1818 
1819                 failed.add( test );
1820             }
1821         }
1822 
1823         System.out.println( tests.size() - failed.size() + "/" + tests.size() + " passed" );
1824         if ( !failed.isEmpty() )
1825         {
1826             System.out.println( "Failed tests: " + failed );
1827         }
1828 
1829         System.exit( exitCode );
1830     }
1831 
1832     private void findLocalRepo( String settingsFile )
1833         throws VerificationException
1834     {
1835         if ( localRepo == null )
1836         {
1837             localRepo = System.getProperty( "maven.repo.local" );
1838         }
1839 
1840         if ( localRepo == null )
1841         {
1842             localRepo = retrieveLocalRepo( settingsFile );
1843         }
1844 
1845         if ( localRepo == null )
1846         {
1847             localRepo = System.getProperty( "user.home" ) + "/.m2/repository";
1848         }
1849 
1850         File repoDir = new File( localRepo );
1851 
1852         if ( !repoDir.exists() )
1853         {
1854             //noinspection ResultOfMethodCallIgnored
1855             repoDir.mkdirs();
1856         }
1857 
1858         // normalize path
1859         localRepo = repoDir.getAbsolutePath();
1860 
1861         localRepoLayout = System.getProperty( "maven.repo.local.layout", "default" );
1862     }
1863 
1864     private static void runIntegrationTest( Verifier verifier )
1865         throws VerificationException
1866     {
1867         verifier.executeHook( "prebuild-hook.txt" );
1868 
1869         Properties properties = verifier.loadProperties( "system.properties" );
1870 
1871         Properties controlProperties = verifier.loadProperties( "verifier.properties" );
1872 
1873         boolean chokeOnErrorOutput = Boolean.valueOf( controlProperties.getProperty( "failOnErrorOutput", "true" ) );
1874 
1875         List<String> goals = verifier.loadFile( verifier.getBasedir(), "goals.txt", false );
1876 
1877         List<String> cliOptions = verifier.loadFile( verifier.getBasedir(), "cli-options.txt", false );
1878 
1879         verifier.setCliOptions( cliOptions );
1880 
1881         verifier.setSystemProperties( properties );
1882 
1883         verifier.setVerifierProperties( controlProperties );
1884 
1885         verifier.executeGoals( goals );
1886 
1887         verifier.executeHook( "postbuild-hook.txt" );
1888 
1889         System.out.println( "*** Verifying: fail when [ERROR] detected? " + chokeOnErrorOutput + " ***" );
1890 
1891         verifier.verify( chokeOnErrorOutput );
1892 
1893         verifier.resetStreams();
1894 
1895         System.out.println( "OK" );
1896     }
1897 
1898     /**
1899      * Verifies that the artifact given by its Maven coordinates exists and contains the given content.
1900      * 
1901      * @param groupId the groupId of the artifact (must not be null)
1902      * @param artifactId the artifactId of the artifact (must not be null)
1903      * @param version the version of the artifact (must not be null)
1904      * @param ext the extension of the artifact (must not be null)
1905      * @param content the expected content
1906      * @throws IOException if reading from the artifact fails 
1907      * @throws VerificationException if the content of the artifact differs
1908      */
1909     public void verifyArtifactContent( String groupId, String artifactId, String version, String ext, String content )
1910         throws IOException, VerificationException
1911     {
1912         String fileName = getArtifactPath( groupId, artifactId, version, ext );
1913         if ( content.equals( FileUtils.fileRead( fileName ) ) )
1914         {
1915             throw new VerificationException( "Content of " + fileName + " does not equal " + content );
1916         }
1917     }
1918 
1919     /**
1920      * @deprecated Use {@link #verifyArtifactContent(String, String, String, String, String)} instead.
1921      */
1922     @Deprecated
1923     public void assertArtifactContents( String org, String artifact, String version, String type, String contents )
1924         throws IOException
1925     {
1926         String fileName = getArtifactPath( org, artifact, version, type );
1927         Assert.assertEquals( contents, FileUtils.fileRead( fileName ) );
1928     }
1929 
1930     static class UserModelReader
1931         extends DefaultHandler
1932     {
1933         private String localRepository;
1934 
1935         private StringBuffer currentBody = new StringBuffer();
1936 
1937         public void parse( File file )
1938             throws VerificationException
1939         {
1940             try
1941             {
1942                 SAXParserFactory saxFactory = SAXParserFactory.newInstance();
1943 
1944                 SAXParser parser = saxFactory.newSAXParser();
1945 
1946                 InputSource is = new InputSource( new FileInputStream( file ) );
1947 
1948                 parser.parse( is, this );
1949             }
1950             catch ( FileNotFoundException e )
1951             {
1952                 throw new VerificationException( "file not found path : " + file.getAbsolutePath(), e );
1953             }
1954             catch ( IOException e )
1955             {
1956                 throw new VerificationException( " IOException path : " + file.getAbsolutePath(), e );
1957             }
1958             catch ( ParserConfigurationException e )
1959             {
1960                 throw new VerificationException( e );
1961             }
1962             catch ( SAXException e )
1963             {
1964                 throw new VerificationException( "Parsing exception for file " + file.getAbsolutePath(), e );
1965             }
1966         }
1967 
1968         public void warning( SAXParseException spe )
1969         {
1970             printParseError( "Warning", spe );
1971         }
1972 
1973         public void error( SAXParseException spe )
1974         {
1975             printParseError( "Error", spe );
1976         }
1977 
1978         public void fatalError( SAXParseException spe )
1979         {
1980             printParseError( "Fatal Error", spe );
1981         }
1982 
1983         private void printParseError( String type, SAXParseException spe )
1984         {
1985             System.err.println(
1986                 type + " [line " + spe.getLineNumber() + ", row " + spe.getColumnNumber() + "]: " + spe.getMessage() );
1987         }
1988 
1989         public String getLocalRepository()
1990         {
1991             return localRepository;
1992         }
1993 
1994         public void characters( char[] ch, int start, int length )
1995             throws SAXException
1996         {
1997             currentBody.append( ch, start, length );
1998         }
1999 
2000         public void endElement( String uri, String localName, String rawName )
2001             throws SAXException
2002         {
2003             if ( "localRepository".equals( rawName ) )
2004             {
2005                 if ( notEmpty( currentBody.toString() ) )
2006                 {
2007                     localRepository = currentBody.toString().trim();
2008                 }
2009                 else
2010                 {
2011                     throw new SAXException(
2012                         "Invalid mavenProfile entry. Missing one or more " + "fields: {localRepository}." );
2013                 }
2014             }
2015 
2016             currentBody = new StringBuffer();
2017         }
2018 
2019         private boolean notEmpty( String test )
2020         {
2021             return test != null && test.trim().length() > 0;
2022         }
2023 
2024         public void reset()
2025         {
2026             currentBody = null;
2027             localRepository = null;
2028         }
2029     }
2030 
2031     public List<String> getCliOptions()
2032     {
2033         return cliOptions;
2034     }
2035 
2036     public void setCliOptions( List<String> cliOptions )
2037     {
2038         this.cliOptions = cliOptions;
2039     }
2040 
2041     public void addCliOption( String option )
2042     {
2043         cliOptions.add( option );
2044     }
2045 
2046     public Properties getSystemProperties()
2047     {
2048         return systemProperties;
2049     }
2050 
2051     public void setSystemProperties( Properties systemProperties )
2052     {
2053         this.systemProperties = systemProperties;
2054     }
2055 
2056     public void setSystemProperty( String key, String value )
2057     {
2058         if ( value != null )
2059         {
2060             systemProperties.setProperty( key, value );
2061         }
2062         else
2063         {
2064             systemProperties.remove( key );
2065         }
2066     }
2067 
2068     public Map<String, String> getEnvironmentVariables()
2069     {
2070         return environmentVariables;
2071     }
2072 
2073     public void setEnvironmentVariables( Map<String, String> environmentVariables )
2074     {
2075         this.environmentVariables = environmentVariables;
2076     }
2077 
2078     public void setEnvironmentVariable( String key, String value )
2079     {
2080         if ( value != null )
2081         {
2082             environmentVariables.put( key, value );
2083         }
2084         else
2085         {
2086             environmentVariables.remove( key );
2087         }
2088     }
2089 
2090     public Properties getVerifierProperties()
2091     {
2092         return verifierProperties;
2093     }
2094 
2095     public void setVerifierProperties( Properties verifierProperties )
2096     {
2097         this.verifierProperties = verifierProperties;
2098     }
2099 
2100     public boolean isAutoclean()
2101     {
2102         return autoclean;
2103     }
2104 
2105     public void setAutoclean( boolean autoclean )
2106     {
2107         this.autoclean = autoclean;
2108     }
2109 
2110     public String getBasedir()
2111     {
2112         return basedir;
2113     }
2114 
2115     /**
2116      * Gets the name of the file used to log build output.
2117      *
2118      * @return The name of the log file, relative to the base directory, never <code>null</code>.
2119      * @since 1.2
2120      */
2121     public String getLogFileName()
2122     {
2123         return this.logFileName;
2124     }
2125 
2126     /**
2127      * Sets the name of the file used to log build output.
2128      *
2129      * @param logFileName The name of the log file, relative to the base directory, must not be empty or
2130      *                    <code>null</code>.
2131      * @since 1.2
2132      */
2133     public void setLogFileName( String logFileName )
2134     {
2135         if ( StringUtils.isEmpty( logFileName ) )
2136         {
2137             throw new IllegalArgumentException( "log file name unspecified" );
2138         }
2139         this.logFileName = logFileName;
2140     }
2141 
2142     public void setDebug( boolean debug )
2143     {
2144         this.debug = debug;
2145 
2146         if ( !debug )
2147         {
2148             System.setOut( new PrintStream( outStream ) );
2149 
2150             System.setErr( new PrintStream( errStream ) );
2151         }
2152     }
2153 
2154     public boolean isMavenDebug()
2155     {
2156         return mavenDebug;
2157     }
2158 
2159     public void setMavenDebug( boolean mavenDebug )
2160     {
2161         this.mavenDebug = mavenDebug;
2162     }
2163 
2164     public void setForkJvm( boolean forkJvm )
2165     {
2166         this.forkJvm = forkJvm;
2167     }
2168 
2169     public boolean isDebugJvm()
2170     {
2171         return debugJvm;
2172     }
2173 
2174     public void setDebugJvm( boolean debugJvm )
2175     {
2176         this.debugJvm = debugJvm;
2177     }
2178 
2179     public String getLocalRepoLayout()
2180     {
2181         return localRepoLayout;
2182     }
2183 
2184     public void setLocalRepoLayout( String localRepoLayout )
2185     {
2186         this.localRepoLayout = localRepoLayout;
2187     }
2188 
2189     public String getLocalRepository()
2190     {
2191         return localRepo;
2192     }
2193 }