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 junit.framework.TestCase;
23  import org.apache.maven.artifact.versioning.ArtifactVersion;
24  import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
25  import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException;
26  import org.apache.maven.artifact.versioning.VersionRange;
27  import org.apache.maven.shared.utils.io.FileUtils;
28  
29  import java.io.File;
30  import java.io.IOException;
31  import java.io.PrintStream;
32  import java.nio.file.Files;
33  import java.nio.file.Path;
34  import java.nio.file.Paths;
35  import java.text.DecimalFormat;
36  import java.text.DecimalFormatSymbols;
37  import java.util.ArrayList;
38  import java.util.Collection;
39  import java.util.Locale;
40  import java.util.regex.Matcher;
41  import java.util.regex.Pattern;
42  
43  /**
44   * @author Jason van Zyl
45   * @author Kenney Westerhof
46   */
47  public abstract class AbstractMavenIntegrationTestCase
48      extends TestCase
49  {
50      /**
51       * Save System.out for progress reports etc.
52       */
53      private static PrintStream out = System.out;
54  
55      /**
56       * The format for elapsed time.
57       */
58      private static final DecimalFormat SECS_FORMAT =
59          new DecimalFormat( "(0.0 s)", new DecimalFormatSymbols( Locale.ENGLISH ) );
60  
61      /**
62       * The zero-based column index where to print the test result.
63       */
64      private static final int RESULT_COLUMN = 60;
65  
66      private boolean skip;
67  
68      private BrokenMavenVersionException invert;
69  
70      private static ArtifactVersion javaVersion;
71  
72      private ArtifactVersion mavenVersion;
73  
74      private VersionRange versionRange;
75  
76      private String matchPattern;
77  
78      private static final String DEFAULT_MATCH_PATTERN = "(.*?)-(RC[0-9]+|SNAPSHOT|RC[0-9]+-SNAPSHOT)";
79  
80      protected static final String ALL_MAVEN_VERSIONS = "[2.0,)";
81  
82      protected AbstractMavenIntegrationTestCase( String versionRangeStr )
83      {
84          this( versionRangeStr, DEFAULT_MATCH_PATTERN );
85      }
86  
87      protected AbstractMavenIntegrationTestCase( String versionRangeStr, String matchPattern )
88      {
89          this.matchPattern = matchPattern;
90  
91          try
92          {
93              versionRange = VersionRange.createFromVersionSpec( versionRangeStr );
94          }
95          catch ( InvalidVersionSpecificationException e )
96          {
97              throw (RuntimeException) new IllegalArgumentException( "Invalid version range: " + versionRangeStr, e );
98          }
99  
100         ArtifactVersion version = getMavenVersion();
101         if ( version != null )
102         {
103             skip = !versionRange.containsVersion( removePattern( version ) );
104         }
105         else
106         {
107             out.println( "WARNING: " + getITName() + ": version range '" + versionRange
108                              + "' supplied but no Maven version - not skipping test." );
109         }
110     }
111 
112     /**
113      * Gets the Java version used to run this test.
114      *
115      * @return The Java version, never <code>null</code>.
116      */
117     private ArtifactVersion getJavaVersion()
118     {
119         if ( javaVersion == null )
120         {
121             String version = System.getProperty( "java.version" );
122             version = version.replaceAll( "[_-]", "." );
123             Matcher matcher = Pattern.compile( "(?s).*?(([0-9]+\\.[0-9]+)(\\.[0-9]+)?).*" ).matcher( version );
124             if ( matcher.matches() )
125             {
126                 version = matcher.group( 1 );
127             }
128             javaVersion = new DefaultArtifactVersion( version );
129         }
130         return javaVersion;
131     }
132 
133     /**
134      * Gets the Maven version used to run this test.
135      *
136      * @return The Maven version or <code>null</code> if unknown.
137      */
138     protected final ArtifactVersion getMavenVersion()
139     {
140         if ( mavenVersion == null )
141         {
142             String version = System.getProperty( "maven.version", "" );
143 
144             if ( version.length() <= 0 || version.startsWith( "${" ) )
145             {
146                 try
147                 {
148                     Verifier verifier = new Verifier( "" );
149                     try
150                     {
151                         version = verifier.getMavenVersion();
152                         System.setProperty( "maven.version", version );
153                     }
154                     finally
155                     {
156                         verifier.resetStreams();
157                     }
158                 }
159                 catch ( VerificationException e )
160                 {
161                     e.printStackTrace();
162                 }
163             }
164 
165             // NOTE: If the version looks like "${...}" it has been configured from an undefined expression
166             if ( version != null && version.length() > 0 && !version.startsWith( "${" ) )
167             {
168                 mavenVersion = new DefaultArtifactVersion( version );
169             }
170         }
171         return mavenVersion;
172     }
173 
174     /**
175      * This allows fine-grained control over execution of individual test methods
176      * by allowing tests to adjust to the current Maven version, or else simply avoid
177      * executing altogether if the wrong version is present.
178      */
179     protected boolean matchesVersionRange( String versionRangeStr )
180     {
181         VersionRange versionRange;
182         try
183         {
184             versionRange = VersionRange.createFromVersionSpec( versionRangeStr );
185         }
186         catch ( InvalidVersionSpecificationException e )
187         {
188             throw (RuntimeException) new IllegalArgumentException( "Invalid version range: " + versionRangeStr, e );
189         }
190 
191         ArtifactVersion version = getMavenVersion();
192         if ( version != null )
193         {
194             return versionRange.containsVersion( removePattern( version ) );
195         }
196         else
197         {
198             out.println( "WARNING: " + getITName() + ": version range '" + versionRange
199                              + "' supplied but no Maven version found - returning true for match check." );
200 
201             return true;
202         }
203     }
204 
205     /**
206      * Can be called by version specific setUp calls
207      *
208      * @return
209      */
210     protected final boolean isSkipped()
211     {
212         return skip;
213     }
214 
215     protected void runTest()
216         throws Throwable
217     {
218         String testName = getTestName();
219 
220         if ( testName.startsWith( "mng" ) || Character.isDigit( testName.charAt( 0 ) ) )
221         {
222             int mng = 4;
223             while ( Character.isDigit( testName.charAt( mng ) ) )
224             {
225                 mng++;
226             }
227             out.print( AnsiSupport.bold( testName.substring( 0, mng ) ) );
228             out.print( ' ' );
229             out.print( testName.substring( mng ) );
230         }
231         else
232         {
233             int index = testName.indexOf( ' ' );
234             if ( index == -1 )
235             {
236                 out.print( testName );
237             }
238             else
239             {
240                 out.print( AnsiSupport.bold( testName.substring( 0, index ) ) );
241                 out.print( testName.substring( index ) );
242             }
243             out.print( '.' );
244         }
245 
246         out.print( pad( RESULT_COLUMN - testName.length() ) );
247         out.print( ' ' );
248 
249         if ( skip )
250         {
251             out.println( AnsiSupport.warning( "SKIPPED" ) + " - Maven version " + getMavenVersion() + " not in range "
252                 + versionRange );
253             return;
254         }
255 
256         if ( "true".equals( System.getProperty( "useEmptyLocalRepository", "false" ) ) )
257         {
258             setupLocalRepo();
259         }
260 
261         invert = null;
262         long milliseconds = System.currentTimeMillis();
263         try
264         {
265             super.runTest();
266             milliseconds = System.currentTimeMillis() - milliseconds;
267             if ( invert != null )
268             {
269                 throw invert;
270             }
271             out.println( AnsiSupport.success( "OK" ) + " " + formatTime( milliseconds ) );
272         }
273         catch ( UnsupportedJavaVersionException e )
274         {
275             out.println( AnsiSupport.warning( "SKIPPED" ) + " - Java version " + e.javaVersion + " not in range "
276                 + e.supportedRange );
277             return;
278         }
279         catch ( UnsupportedMavenVersionException e )
280         {
281             out.println( AnsiSupport.warning( "SKIPPED" ) + " - Maven version " + e.mavenVersion + " not in range "
282                 + e.supportedRange );
283             return;
284         }
285         catch ( BrokenMavenVersionException e )
286         {
287             out.println( AnsiSupport.error( "UNEXPECTED OK" ) + " - Maven version " + e.mavenVersion
288                 + " expected to fail " + formatTime( milliseconds ) );
289             fail( "Expected failure when with Maven version " + e.mavenVersion );
290         }
291         catch ( Throwable t )
292         {
293             milliseconds = System.currentTimeMillis() - milliseconds;
294             if ( invert != null )
295             {
296                 out.println( AnsiSupport.success( "EXPECTED FAIL" ) + " - Maven version " + invert.mavenVersion
297                     + " expected to fail " + formatTime( milliseconds ) );
298             }
299             else
300             {
301                 out.println( AnsiSupport.error( "FAILURE" ) + " " + formatTime( milliseconds ) );
302                 throw t;
303             }
304         }
305     }
306 
307     /**
308      * Guards the execution of a test case by checking that the current Java version matches the specified version
309      * range. If the check fails, an exception will be thrown which aborts the current test and marks it as skipped. One
310      * would usually call this method right at the start of a test method.
311      *
312      * @param versionRange The version range that specifies the acceptable Java versions for the test, must not be
313      *                     <code>null</code>.
314      */
315     protected void requiresJavaVersion( String versionRange )
316     {
317         VersionRange range;
318         try
319         {
320             range = VersionRange.createFromVersionSpec( versionRange );
321         }
322         catch ( InvalidVersionSpecificationException e )
323         {
324             throw (RuntimeException) new IllegalArgumentException( "Invalid version range: " + versionRange, e );
325         }
326 
327         ArtifactVersion version = getJavaVersion();
328         if ( !range.containsVersion( version ) )
329         {
330             throw new UnsupportedJavaVersionException( version, range );
331         }
332     }
333 
334     /**
335      * Guards the execution of a test case by checking that the current Maven version matches the specified version
336      * range. If the check fails, an exception will be thrown which aborts the current test and marks it as skipped. One
337      * would usually call this method right at the start of a test method.
338      *
339      * @param versionRange The version range that specifies the acceptable Maven versions for the test, must not be
340      *                     <code>null</code>.
341      */
342     protected void requiresMavenVersion( String versionRange )
343     {
344         VersionRange range;
345         try
346         {
347             range = VersionRange.createFromVersionSpec( versionRange );
348         }
349         catch ( InvalidVersionSpecificationException e )
350         {
351             throw (RuntimeException) new IllegalArgumentException( "Invalid version range: " + versionRange, e );
352         }
353 
354         ArtifactVersion version = getMavenVersion();
355         if ( version != null )
356         {
357             if ( !range.containsVersion( removePattern( version ) ) )
358             {
359                 throw new UnsupportedMavenVersionException( version, range );
360             }
361         }
362         else
363         {
364             out.println( "WARNING: " + getITName() + ": version range '" + versionRange
365                              + "' supplied but no Maven version found - not skipping test." );
366         }
367     }
368 
369     /**
370      * Inverts the execution of a test case for cases where we discovered a bug in the test case, have corrected the
371      * test case and shipped versions of Maven with a bug because of the faulty test case. This method allows the
372      * tests to continue passing against the historical releases as they historically would, as well as verifying that
373      * the test is no longer providing a false positive.
374      *
375      * @param versionRange
376      */
377     protected void failingMavenVersions( String versionRange )
378     {
379         assertNull( "Only call failingMavenVersions at most once per test", invert );
380         VersionRange range;
381         try
382         {
383             range = VersionRange.createFromVersionSpec( versionRange );
384         }
385         catch ( InvalidVersionSpecificationException e )
386         {
387             throw (RuntimeException) new IllegalArgumentException( "Invalid version range: " + versionRange, e );
388         }
389 
390         ArtifactVersion version = getMavenVersion();
391         if ( version != null )
392         {
393             if ( range.containsVersion( removePattern( version ) ) )
394             {
395                 invert = new BrokenMavenVersionException( version, range );
396             }
397         }
398         else
399         {
400             out.println( "WARNING: " + getITName() + ": version range '" + versionRange
401                              + "' supplied but no Maven version found - not marking test as expected to fail." );
402         }
403     }
404 
405     private class UnsupportedJavaVersionException
406         extends RuntimeException
407     {
408         @SuppressWarnings( "checkstyle:visibilitymodifier" )
409         public ArtifactVersion javaVersion;
410 
411         @SuppressWarnings( "checkstyle:visibilitymodifier" )
412         public VersionRange supportedRange;
413 
414         private UnsupportedJavaVersionException( ArtifactVersion javaVersion, VersionRange supportedRange )
415         {
416             this.javaVersion = javaVersion;
417             this.supportedRange = supportedRange;
418         }
419 
420     }
421 
422     private class UnsupportedMavenVersionException
423         extends RuntimeException
424     {
425         @SuppressWarnings( "checkstyle:visibilitymodifier" )
426         public ArtifactVersion mavenVersion;
427 
428         @SuppressWarnings( "checkstyle:visibilitymodifier" )
429         public VersionRange supportedRange;
430 
431         private UnsupportedMavenVersionException( ArtifactVersion mavenVersion, VersionRange supportedRange )
432         {
433             this.mavenVersion = mavenVersion;
434             this.supportedRange = supportedRange;
435         }
436 
437     }
438 
439     private class BrokenMavenVersionException
440         extends RuntimeException
441     {
442         @SuppressWarnings( "checkstyle:visibilitymodifier" )
443         public ArtifactVersion mavenVersion;
444 
445         @SuppressWarnings( "checkstyle:visibilitymodifier" )
446         public VersionRange supportedRange;
447 
448         private BrokenMavenVersionException( ArtifactVersion mavenVersion, VersionRange supportedRange )
449         {
450             this.mavenVersion = mavenVersion;
451             this.supportedRange = supportedRange;
452         }
453 
454     }
455 
456     private String getITName()
457     {
458         String simpleName = getClass().getName();
459         int idx = simpleName.lastIndexOf( '.' );
460         simpleName = idx >= 0 ? simpleName.substring( idx + 1 ) : simpleName;
461         simpleName = simpleName.startsWith( "MavenIT" ) ? simpleName.substring( "MavenIT".length() ) : simpleName;
462         simpleName = simpleName.endsWith( "Test" ) ? simpleName.substring( 0, simpleName.length() - 4 ) : simpleName;
463         return simpleName;
464     }
465 
466     private String getTestName()
467     {
468         String className = getITName();
469         String methodName = getName();
470         if ( methodName.startsWith( "test" ) )
471         {
472             methodName = methodName.substring( 4 );
473         }
474         return className + '.' + methodName + "()";
475     }
476 
477     private String pad( int chars )
478     {
479         StringBuilder buffer = new StringBuilder( 128 );
480         for ( int i = 0; i < chars; i++ )
481         {
482             buffer.append( '.' );
483         }
484         return buffer.toString();
485     }
486 
487     private String formatTime( long milliseconds )
488     {
489         return SECS_FORMAT.format( milliseconds / 1000.0 );
490     }
491 
492     protected File setupLocalRepo()
493         throws IOException
494     {
495         String tempDirPath = System.getProperty( "maven.it.tmpdir", System.getProperty( "java.io.tmpdir" ) );
496         File localRepo = new File( tempDirPath, "local-repository/" + getITName() );
497         if ( localRepo.isDirectory() )
498         {
499             FileUtils.deleteDirectory( localRepo );
500         }
501 
502         System.setProperty( "maven.repo.local", localRepo.getAbsolutePath() );
503 
504         return localRepo;
505     }
506 
507     ArtifactVersion removePattern( ArtifactVersion version )
508     {
509         String v = version.toString();
510 
511         Matcher m = Pattern.compile( matchPattern ).matcher( v );
512 
513         if ( m.matches() )
514         {
515             return new DefaultArtifactVersion( m.group( 1 ) );
516         }
517         return version;
518     }
519 
520     protected Verifier newVerifier( String basedir )
521         throws VerificationException
522     {
523         return newVerifier( basedir, false );
524     }
525 
526     protected Verifier newVerifier( String basedir, String settings )
527         throws VerificationException
528     {
529         return newVerifier( basedir, settings, false );
530     }
531 
532     protected Verifier newVerifier( String basedir, boolean debug )
533         throws VerificationException
534     {
535         return newVerifier( basedir, "", debug );
536     }
537 
538     protected Verifier newVerifier( String basedir, String settings, boolean debug )
539         throws VerificationException
540     {
541         Verifier verifier = new Verifier( basedir, debug );
542 
543         verifier.setAutoclean( false );
544 
545         if ( settings != null )
546         {
547             File settingsFile;
548             if ( settings.length() > 0 )
549             {
550                 settingsFile = new File( "settings-" + settings + ".xml" );
551             }
552             else
553             {
554                 settingsFile = new File( "settings.xml" );
555             }
556 
557             if ( !settingsFile.isAbsolute() )
558             {
559                 String settingsDir = System.getProperty( "maven.it.global-settings.dir", "" );
560                 if ( settingsDir.length() > 0 )
561                 {
562                     settingsFile = new File( settingsDir, settingsFile.getPath() );
563                 }
564                 else
565                 {
566                     //
567                     // Make is easier to run ITs from m2e in Maven IT mode without having to set any additional
568                     // properties.
569                     //
570                     settingsFile = new File( "target/test-classes", settingsFile.getPath() );
571                 }
572             }
573 
574             String path = settingsFile.getAbsolutePath();
575 
576             // dedicated CLI option only available since MNG-3914
577             if ( matchesVersionRange( "[2.1.0,)" ) )
578             {
579                 verifier.getCliOptions().add( "--global-settings" );
580                 if ( path.indexOf( ' ' ) < 0 )
581                 {
582                     verifier.getCliOptions().add( path );
583                 }
584                 else
585                 {
586                     verifier.getCliOptions().add( '"' + path + '"' );
587                 }
588             }
589             else
590             {
591                 verifier.getSystemProperties().put( "org.apache.maven.global-settings", path );
592             }
593         }
594 
595         if ( matchesVersionRange( "(3.2.5,)" ) )
596         {
597             String multiModuleProjectDirectory = findMultiModuleProjectDirectory( basedir );
598             verifier.getSystemProperties().put( "maven.multiModuleProjectDirectory", multiModuleProjectDirectory );
599         }
600 
601         try
602         {
603             // auto set source+target to lowest reasonable java version
604             // Java9 requires at least 1.6
605             if ( VersionRange.createFromVersionSpec( "[9,12)" ).containsVersion( getJavaVersion() ) )
606             {
607                 verifier.getSystemProperties().put( "maven.compiler.source", "1.7" );
608                 verifier.getSystemProperties().put( "maven.compiler.target", "1.7" );
609                 verifier.getSystemProperties().put( "maven.compiler.release", "7" );
610             }
611             // Java12 requires at least 7
612             if ( VersionRange.createFromVersionSpec( "[12,)" ).containsVersion( getJavaVersion() ) )
613             {
614                 verifier.getSystemProperties().put( "maven.compiler.source", "7" );
615                 verifier.getSystemProperties().put( "maven.compiler.target", "7" );
616                 verifier.getSystemProperties().put( "maven.compiler.release", "7" );
617             }
618         }
619         catch ( InvalidVersionSpecificationException e )
620         {
621             // noop
622         }
623 
624         return verifier;
625     }
626 
627     private boolean hasDotMvnSubDirectory( Path path )
628     {
629         final Path probablySubDirectory = path.resolve( ".mvn" );
630         return Files.exists( probablySubDirectory ) && Files.isDirectory( probablySubDirectory );
631     }
632 
633     private String findMultiModuleProjectDirectory( String basedir )
634     {
635         Path path = Paths.get( basedir );
636         Path result = path;
637 
638         Collection<Path> fileSystemRoots = new ArrayList<>();
639         for ( Path root : path.getFileSystem().getRootDirectories() )
640         {
641             fileSystemRoots.add( root );
642         }
643 
644         while ( !fileSystemRoots.contains( path ) )
645         {
646             if ( hasDotMvnSubDirectory( path ) )
647             {
648                 result = path;
649                 break;
650             }
651             path = path.getParent();
652         }
653 
654         return result.toString();
655     }
656 
657     public static void assertCanonicalFileEquals( String message, File expected, File actual )
658         throws IOException
659     {
660         assertEquals( message, expected.getCanonicalFile(), actual.getCanonicalFile() );
661     }
662 
663     public static void assertCanonicalFileEquals( File expected, File actual )
664         throws IOException
665     {
666         assertCanonicalFileEquals( null, expected, actual );
667     }
668 
669     public static void assertCanonicalFileEquals( String message, String expected, String actual )
670         throws IOException
671     {
672         assertCanonicalFileEquals( message, new File( expected ), new File( actual ) );
673     }
674 
675     public static void assertCanonicalFileEquals( String expected, String actual )
676         throws IOException
677     {
678         assertCanonicalFileEquals( null, new File( expected ), new File( actual ) );
679     }
680 }