View Javadoc

1   package org.apache.maven.surefire.booter;
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.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.lang.reflect.InvocationTargetException;
30  import java.lang.reflect.Method;
31  import java.net.MalformedURLException;
32  import java.net.URL;
33  import java.util.ArrayList;
34  import java.util.Collections;
35  import java.util.Enumeration;
36  import java.util.Iterator;
37  import java.util.List;
38  import java.util.Map;
39  import java.util.Properties;
40  import java.util.SortedMap;
41  import java.util.TreeMap;
42  
43  import org.apache.maven.surefire.Surefire;
44  import org.apache.maven.surefire.booter.output.FileOutputConsumerProxy;
45  import org.apache.maven.surefire.booter.output.ForkingStreamConsumer;
46  import org.apache.maven.surefire.booter.output.OutputConsumer;
47  import org.apache.maven.surefire.booter.output.StandardOutputConsumer;
48  import org.apache.maven.surefire.booter.output.SupressFooterOutputConsumerProxy;
49  import org.apache.maven.surefire.booter.output.SupressHeaderOutputConsumerProxy;
50  import org.apache.maven.surefire.testset.TestSetFailedException;
51  import org.apache.maven.surefire.util.NestedRuntimeException;
52  import org.apache.maven.surefire.util.UrlUtils;
53  import org.codehaus.plexus.util.IOUtil;
54  import org.codehaus.plexus.util.StringUtils;
55  import org.codehaus.plexus.util.cli.CommandLineException;
56  import org.codehaus.plexus.util.cli.CommandLineUtils;
57  import org.codehaus.plexus.util.cli.Commandline;
58  import org.codehaus.plexus.util.cli.StreamConsumer;
59  
60  /**
61   * @author Jason van Zyl
62   * @author Emmanuel Venisse
63   * @version $Id: SurefireBooter.java 980568 2010-07-29 20:56:29Z krosenvold $
64   */
65  public class SurefireBooter
66  {
67      private static final String TEST_SUITE_PROPERTY_PREFIX = "testSuite.";
68      private static final String REPORT_PROPERTY_PREFIX = "report.";
69      private static final String PARAMS_SUFIX = ".params";
70      private static final String TYPES_SUFIX = ".types";
71  
72      private List reports = new ArrayList();
73  
74      private List classPathUrls = new ArrayList();
75  
76      private List surefireClassPathUrls = new ArrayList();
77  
78      private List surefireBootClassPathUrls = new ArrayList();
79  
80      private List testSuites = new ArrayList();
81      
82      private boolean failIfNoTests = false;
83      
84      private int forkedProcessTimeoutInSeconds = 0;
85  
86      private boolean redirectTestOutputToFile = false;
87  
88      // ----------------------------------------------------------------------
89      //
90      // ----------------------------------------------------------------------
91  
92      private ForkConfiguration forkConfiguration;
93  
94      public static final int TESTS_SUCCEEDED_EXIT_CODE = 0;
95  
96      public static final int TESTS_FAILED_EXIT_CODE = 255;
97      
98      public static final int NO_TESTS_EXIT_CODE = 254;
99  
100     private static Method assertionStatusMethod;
101 
102     /**
103      * @deprecated because the IsolatedClassLoader is really isolated - no parent.
104      */
105     private boolean childDelegation = true;
106 
107     private File reportsDirectory;
108 
109     /**
110      * This field is set to true if it's running from main. It's used to help decide what classloader to use.
111      */
112     private final boolean isForked;
113 
114     /**
115      * Whether to enable assertions or not (can be affected by the fork arguments, and the ability to do so based on the
116      * JVM).
117      */
118     private boolean enableAssertions;
119 
120     static
121     {
122         try
123         {
124             assertionStatusMethod =
125                 ClassLoader.class.getMethod( "setDefaultAssertionStatus", new Class[] { boolean.class } );
126         }
127         catch ( NoSuchMethodException e )
128         {
129             assertionStatusMethod = null;
130         }
131     }
132 
133     public SurefireBooter()
134     {
135         isForked = false;
136     }
137 
138     private SurefireBooter( boolean isForked )
139     {
140         this.isForked = isForked;
141     }
142 
143     // ----------------------------------------------------------------------
144     // Accessors
145     // ----------------------------------------------------------------------
146 
147     public void addReport( String report )
148     {
149         addReport( report, null );
150     }
151 
152     public void addReport( String report, Object[] constructorParams )
153     {
154         reports.add( new Object[] { report, constructorParams } );
155     }
156 
157     public void addTestSuite( String suiteClassName, Object[] constructorParams )
158     {
159         testSuites.add( new Object[] { suiteClassName, constructorParams } );
160     }
161 
162     public void addClassPathUrl( String path )
163     {
164         if ( !classPathUrls.contains( path ) )
165         {
166             classPathUrls.add( path );
167         }
168     }
169 
170     public void addSurefireClassPathUrl( String path )
171     {
172         if ( !surefireClassPathUrls.contains( path ) )
173         {
174             surefireClassPathUrls.add( path );
175         }
176     }
177 
178     public void addSurefireBootClassPathUrl( String path )
179     {
180         if ( !surefireBootClassPathUrls.contains( path ) )
181         {
182             surefireBootClassPathUrls.add( path );
183         }
184     }
185 
186     /**
187      * Setting this to true will cause a failure if there are no tests to run
188      *
189      * @param redirectTestOutputToFile
190      */
191     public void setFailIfNoTests( boolean failIfNoTests )
192     {
193         this.failIfNoTests = failIfNoTests;
194     }
195     
196     /**
197      * When forking, setting this to true will make the test output to be saved in a file instead of showing it on the
198      * standard output
199      *
200      * @param redirectTestOutputToFile
201      */
202     public void setRedirectTestOutputToFile( boolean redirectTestOutputToFile )
203     {
204         this.redirectTestOutputToFile = redirectTestOutputToFile;
205     }
206 
207     /**
208      * Set the directory where reports will be saved
209      *
210      * @param reportsDirectory the directory
211      */
212     public void setReportsDirectory( File reportsDirectory )
213     {
214         this.reportsDirectory = reportsDirectory;
215     }
216 
217     /**
218      * Get the directory where reports will be saved
219      */
220     public File getReportsDirectory()
221     {
222         return reportsDirectory;
223     }
224 
225     public void setForkConfiguration( ForkConfiguration forkConfiguration )
226     {
227         this.forkConfiguration = forkConfiguration;
228     }
229     
230     public boolean isForking()
231     {
232         return forkConfiguration.isForking();
233     }
234 
235     public int run()
236         throws SurefireBooterForkException, SurefireExecutionException
237     {
238         int result;
239 
240         if (  ForkConfiguration.FORK_NEVER.equals( forkConfiguration.getForkMode() ) )
241         {
242             result = runSuitesInProcess();
243         }
244         else if ( ForkConfiguration.FORK_ONCE.equals( forkConfiguration.getForkMode() ) )
245         {
246             result = runSuitesForkOnce();
247         }
248         else if ( ForkConfiguration.FORK_ALWAYS.equals( forkConfiguration.getForkMode() ) )
249         {
250             result = runSuitesForkPerTestSet();
251         }
252         else
253         {
254             throw new SurefireExecutionException( "Unknown forkmode: " + forkConfiguration.getForkMode(), null );
255         }
256         return result;
257     }
258 
259     private int runSuitesInProcess( String testSet, Properties results )
260         throws SurefireExecutionException
261     {
262         if ( testSuites.size() != 1 )
263         {
264             throw new IllegalArgumentException( "Cannot only specify testSet for single test suites" );
265         }
266 
267         // TODO: replace with plexus
268 
269         // noinspection CatchGenericClass,OverlyBroadCatchBlock
270         ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
271         try
272         {
273             ClassLoader testsClassLoader =
274                 useSystemClassLoader() ? ClassLoader.getSystemClassLoader() : createClassLoader( classPathUrls, null,
275                                                                                                  childDelegation );
276 
277             // TODO: assertions = true shouldn't be required for this CL if we had proper separation (see TestNG)
278             ClassLoader surefireClassLoader = createClassLoader( surefireClassPathUrls, testsClassLoader );
279 
280             Class surefireClass = surefireClassLoader.loadClass( Surefire.class.getName() );
281 
282             Object surefire = surefireClass.newInstance();
283 
284             Method run =
285                 surefireClass.getMethod( "run", new Class[] { List.class, Object[].class, String.class,
286                     ClassLoader.class, ClassLoader.class, Properties.class, Boolean.class } );
287 
288             Thread.currentThread().setContextClassLoader( testsClassLoader );
289 
290             Integer result =
291                 (Integer) run.invoke( surefire, new Object[] { reports, testSuites.get( 0 ), testSet,
292                     surefireClassLoader, testsClassLoader, results, new Boolean( failIfNoTests ) } );
293 
294             return result.intValue();
295         }
296         catch ( InvocationTargetException e )
297         {
298             throw new SurefireExecutionException( e.getTargetException().getMessage(), e.getTargetException() );
299         }
300         catch ( Exception e )
301         {
302             throw new SurefireExecutionException( "Unable to instantiate and execute Surefire", e );
303         }
304         finally
305         {
306             Thread.currentThread().setContextClassLoader( oldContextClassLoader );
307         }
308     }
309 
310     private int runSuitesInProcess()
311         throws SurefireExecutionException
312     {
313         // TODO: replace with plexus
314 
315         // noinspection CatchGenericClass,OverlyBroadCatchBlock
316         ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
317 
318         try
319         {
320             // The test classloader must be constructed first to avoid issues with commons-logging until we properly
321             // separate the TestNG classloader
322             ClassLoader testsClassLoader;
323             String testClassPath = getTestClassPathAsString();
324             System.setProperty( "surefire.test.class.path", testClassPath );
325             if ( useManifestOnlyJar() )
326             {
327                 testsClassLoader = getClass().getClassLoader(); // ClassLoader.getSystemClassLoader()
328                 // SUREFIRE-459, trick the app under test into thinking its classpath was conventional
329                 // (instead of a single manifest-only jar) 
330                 System.setProperty( "surefire.real.class.path", System.getProperty( "java.class.path" ) );
331                 System.setProperty( "java.class.path", testClassPath );
332             }
333             else
334             {
335                 testsClassLoader = createClassLoader( classPathUrls, null, childDelegation );
336             }
337             
338             ClassLoader surefireClassLoader = createClassLoader( surefireClassPathUrls, testsClassLoader );
339 
340             Class surefireClass = surefireClassLoader.loadClass( Surefire.class.getName() );
341 
342             Object surefire = surefireClass.newInstance();
343 
344             Method run =
345                 surefireClass.getMethod( "run", new Class[] { List.class, List.class, ClassLoader.class,
346                     ClassLoader.class, Boolean.class } );
347 
348             Thread.currentThread().setContextClassLoader( testsClassLoader );
349 
350             Integer result =
351                 (Integer) run.invoke( surefire, new Object[] { reports, testSuites, surefireClassLoader,
352                     testsClassLoader, new Boolean( failIfNoTests ) } );
353 
354             return result.intValue();
355         }
356         catch ( InvocationTargetException e )
357         {
358             throw new SurefireExecutionException( e.getTargetException().getMessage(), e.getTargetException() );
359         }
360         catch ( Exception e )
361         {
362             throw new SurefireExecutionException( "Unable to instantiate and execute Surefire", e );
363         }
364         finally
365         {
366             Thread.currentThread().setContextClassLoader( oldContextClassLoader );
367         }
368     }
369 
370     
371     
372     private String getTestClassPathAsString()
373     {
374         StringBuffer sb = new StringBuffer();
375         for ( int i = 0; i < classPathUrls.size(); i++ )
376         {
377             sb.append( classPathUrls.get( i ) ).append( File.pathSeparatorChar );
378         }
379         return sb.toString();
380     }
381     
382     private int runSuitesForkOnce()
383         throws SurefireBooterForkException
384     {
385         return forkSuites( testSuites, true, true );
386     }
387 
388     private int runSuitesForkPerTestSet()
389         throws SurefireBooterForkException
390     {
391         ClassLoader testsClassLoader;
392         ClassLoader surefireClassLoader;
393         try
394         {
395             testsClassLoader = createClassLoader( classPathUrls, null, false );
396             // TODO: assertions = true shouldn't be required if we had proper separation (see TestNG)
397             surefireClassLoader = createClassLoader( surefireClassPathUrls, testsClassLoader, false );
398         }
399         catch ( MalformedURLException e )
400         {
401             throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
402         }
403 
404         int globalResult = 0;
405 
406         boolean showHeading = true;
407         Properties properties = new Properties();
408         for ( Iterator i = testSuites.iterator(); i.hasNext(); )
409         {
410             Object[] testSuite = (Object[]) i.next();
411 
412             Map testSets = getTestSets( testSuite, testsClassLoader, surefireClassLoader );
413 
414             for ( Iterator j = testSets.keySet().iterator(); j.hasNext(); )
415             {
416                 Object testSet = j.next();
417                 boolean showFooter = !j.hasNext() && !i.hasNext();
418                 int result = forkSuite( testSuite, testSet, showHeading, showFooter, properties );
419                 if ( result > globalResult )
420                 {
421                     globalResult = result;
422                 }
423                 showHeading = false;
424             }
425         }
426 
427         return globalResult;
428     }
429 
430     private Map getTestSets( Object[] testSuite, ClassLoader testsClassLoader, ClassLoader surefireClassLoader )
431         throws SurefireBooterForkException
432     {
433         String className = (String) testSuite[0];
434 
435         Object[] params = (Object[]) testSuite[1];
436 
437         Object suite;
438         try
439         {
440             suite = Surefire.instantiateObject( className, params, surefireClassLoader );
441         }
442         catch ( TestSetFailedException e )
443         {
444             throw new SurefireBooterForkException( e.getMessage(), e.getCause() );
445         }
446         catch ( ClassNotFoundException e )
447         {
448             throw new SurefireBooterForkException( "Unable to find class for test suite '" + className + "'", e );
449         }
450         catch ( NoSuchMethodException e )
451         {
452             throw new SurefireBooterForkException( "Unable to find appropriate constructor for test suite '"
453                 + className + "': " + e.getMessage(), e );
454         }
455 
456         Map testSets;
457         try
458         {
459             Method m = suite.getClass().getMethod( "locateTestSets", new Class[] { ClassLoader.class } );
460 
461             testSets = (Map) m.invoke( suite, new Object[] { testsClassLoader } );
462         }
463         catch ( IllegalAccessException e )
464         {
465             throw new SurefireBooterForkException( "Error obtaining test sets", e );
466         }
467         catch ( NoSuchMethodException e )
468         {
469             throw new SurefireBooterForkException( "Error obtaining test sets", e );
470         }
471         catch ( InvocationTargetException e )
472         {
473             throw new SurefireBooterForkException( e.getTargetException().getMessage(), e.getTargetException() );
474         }
475         return testSets;
476     }
477 
478     private int forkSuites( List testSuites, boolean showHeading, boolean showFooter )
479         throws SurefireBooterForkException
480     {
481         Properties properties = new Properties();
482 
483         setForkProperties( testSuites, properties );
484 
485         return fork( properties, showHeading, showFooter );
486     }
487 
488     private int forkSuite( Object[] testSuite, Object testSet, boolean showHeading, boolean showFooter,
489                                Properties properties )
490         throws SurefireBooterForkException
491     {
492         setForkProperties( Collections.singletonList( testSuite ), properties );
493 
494         if ( testSet instanceof String )
495         {
496             properties.setProperty( "testSet", (String) testSet );
497         }
498 
499         return fork( properties, showHeading, showFooter );
500     }
501 
502     private void setForkProperties( List testSuites, Properties properties )
503     {
504         addPropertiesForTypeHolder( reports, properties, REPORT_PROPERTY_PREFIX );
505         addPropertiesForTypeHolder( testSuites, properties, TEST_SUITE_PROPERTY_PREFIX );
506 
507         for ( int i = 0; i < classPathUrls.size(); i++ )
508         {
509             String url = (String) classPathUrls.get( i );
510             properties.setProperty( "classPathUrl." + i, url );
511         }
512 
513         for ( int i = 0; i < surefireClassPathUrls.size(); i++ )
514         {
515             String url = (String) surefireClassPathUrls.get( i );
516             properties.setProperty( "surefireClassPathUrl." + i, url );
517         }
518 
519         properties.setProperty( "childDelegation", String.valueOf( childDelegation ) );
520         properties.setProperty( "enableAssertions", String.valueOf( enableAssertions ) );
521         properties.setProperty( "useSystemClassLoader", String.valueOf( useSystemClassLoader() ) );
522         properties.setProperty( "useManifestOnlyJar", String.valueOf( useManifestOnlyJar() ) );
523         properties.setProperty( "failIfNoTests", String.valueOf( failIfNoTests ) );
524     }
525 
526     private File writePropertiesFile( String name, Properties properties )
527         throws IOException
528     {
529         File file = File.createTempFile( name, "tmp" );
530         if ( !forkConfiguration.isDebug() )
531         {
532             file.deleteOnExit();
533         }
534 
535         writePropertiesFile( file, name, properties );
536 
537         return file;
538     }
539 
540     private void writePropertiesFile( File file, String name, Properties properties )
541         throws IOException
542     {
543         FileOutputStream out = new FileOutputStream( file );
544 
545         try
546         {
547             properties.store( out, name );
548         }
549         finally
550         {
551             IOUtil.close( out );
552         }
553     }
554 
555     private void addPropertiesForTypeHolder( List typeHolderList, Properties properties, String propertyPrefix )
556     {
557         for ( int i = 0; i < typeHolderList.size(); i++ )
558         {
559             Object[] report = (Object[]) typeHolderList.get( i );
560 
561             String className = (String) report[0];
562             Object[] params = (Object[]) report[1];
563 
564             properties.setProperty( propertyPrefix + i, className );
565 
566             if ( params != null )
567             {
568                 String paramProperty = convert( params[0] );
569                 String typeProperty = params[0].getClass().getName();
570                 for ( int j = 1; j < params.length; j++ )
571                 {
572                     paramProperty += "|";
573                     typeProperty += "|";
574                     if ( params[j] != null )
575                     {
576                         paramProperty += convert( params[j] );
577                         typeProperty += params[j].getClass().getName();
578                     }
579                 }
580                 properties.setProperty( propertyPrefix + i + PARAMS_SUFIX, paramProperty );
581                 properties.setProperty( propertyPrefix + i + TYPES_SUFIX, typeProperty );
582             }
583         }
584     }
585 
586     private static String convert( Object param )
587     {
588         if ( param instanceof File[] )
589         {
590             File[] files = (File[]) param;
591             return "[" + StringUtils.join( files, "," ) + "]";
592         }
593         else if ( param instanceof Properties )
594         {
595             ByteArrayOutputStream baos = new ByteArrayOutputStream();
596             try
597             {
598                 ( (Properties) param ).store( baos, "" );
599                 return new String( baos.toByteArray(), "8859_1" );
600             }
601             catch ( Exception e )
602             {
603                 throw new RuntimeException ( "bug in property conversion", e );
604             }
605         }
606         else
607         {
608             return param.toString();
609         }
610     }
611 
612     private boolean useSystemClassLoader()
613     {
614         return forkConfiguration.isUseSystemClassLoader() && ( isForked || forkConfiguration.isForking() );
615     }
616     
617     private boolean useManifestOnlyJar()
618     {
619         return forkConfiguration.isUseSystemClassLoader() && forkConfiguration.isUseManifestOnlyJar();
620     }
621 
622     private int fork( Properties properties, boolean showHeading, boolean showFooter )
623         throws SurefireBooterForkException
624     {
625         File surefireProperties;
626         File systemProperties = null;
627         try
628         {
629             surefireProperties = writePropertiesFile( "surefire", properties );
630             if ( forkConfiguration.getSystemProperties() != null )
631             {
632                 systemProperties = writePropertiesFile( "surefire", forkConfiguration.getSystemProperties() );
633             }
634         }
635         catch ( IOException e )
636         {
637             throw new SurefireBooterForkException( "Error creating properties files for forking", e );
638         }
639 
640         List bootClasspath = new ArrayList( surefireBootClassPathUrls.size() + classPathUrls.size() );
641 
642         bootClasspath.addAll( surefireBootClassPathUrls );
643 
644         if ( useSystemClassLoader() )
645         {
646             bootClasspath.addAll( classPathUrls );
647         }
648 
649         Commandline cli = forkConfiguration.createCommandLine( bootClasspath, useManifestOnlyJar() );
650 
651         cli.createArg().setFile( surefireProperties );
652 
653         if ( systemProperties != null )
654         {
655             cli.createArg().setFile( systemProperties );
656         }
657 
658         
659         ForkingStreamConsumer out = getForkingStreamConsumer( showHeading, showFooter, redirectTestOutputToFile );
660 
661         StreamConsumer err;
662         
663         if ( redirectTestOutputToFile )
664         {
665             err = out;
666         }
667         else
668         { 
669             err = getForkingStreamConsumer( showHeading, showFooter, redirectTestOutputToFile );
670         }
671 
672         if ( forkConfiguration.isDebug() )
673         {
674             System.out.println( "Forking command line: " + cli );
675         }
676 
677         int returnCode;
678 
679         try
680         {
681             returnCode = CommandLineUtils.executeCommandLine( cli, out, err, forkedProcessTimeoutInSeconds );
682         }
683         catch ( CommandLineException e )
684         {
685             throw new SurefireBooterForkException( "Error while executing forked tests.", e );
686         }
687 
688         if ( redirectTestOutputToFile )
689         {
690             // ensure the FileOutputConsumerProxy flushes/closes the output file
691             try
692             {
693                 out.getOutputConsumer().testSetCompleted();
694             }
695             catch ( Exception e )
696             {
697                 // the FileOutputConsumerProxy might throw an IllegalStateException but that's not of interest now
698             }
699         }
700 
701         if ( surefireProperties != null && surefireProperties.exists() )
702         {
703             FileInputStream inStream = null;
704             try
705             {
706                 inStream = new FileInputStream( surefireProperties );
707 
708                 properties.load( inStream );
709             }
710             catch ( FileNotFoundException e )
711             {
712                 throw new SurefireBooterForkException( "Unable to reload properties file from forked process", e );
713             }
714             catch ( IOException e )
715             {
716                 throw new SurefireBooterForkException( "Unable to reload properties file from forked process", e );
717             }
718             finally
719             {
720                 IOUtil.close( inStream );
721             }
722         }
723 
724         return returnCode;
725     }
726 
727     private ClassLoader createClassLoader( List classPathUrls, ClassLoader parent )
728         throws MalformedURLException
729     {
730         return createClassLoader( classPathUrls, parent, false );
731     }
732 
733     private ClassLoader createClassLoader( List classPathUrls, ClassLoader parent, boolean childDelegation )
734         throws MalformedURLException
735     {
736         List urls = new ArrayList();
737 
738         for ( Iterator i = classPathUrls.iterator(); i.hasNext(); )
739         {
740             String url = (String) i.next();
741 
742             if ( url != null )
743             {
744                 File f = new File( url );
745                 urls.add( UrlUtils.getURL( f ) );
746             }
747         }
748 
749         IsolatedClassLoader classLoader = new IsolatedClassLoader( parent, childDelegation );
750         if ( assertionStatusMethod != null )
751         {
752             try
753             {
754                 Object[] args = new Object[] { enableAssertions ? Boolean.TRUE : Boolean.FALSE };
755                 if ( parent != null )
756                 {
757                     assertionStatusMethod.invoke( parent, args );
758                 }
759                 assertionStatusMethod.invoke( classLoader, args );
760             }
761             catch ( IllegalAccessException e )
762             {
763                 throw new NestedRuntimeException( "Unable to access the assertion enablement method", e );
764             }
765             catch ( InvocationTargetException e )
766             {
767                 throw new NestedRuntimeException( "Unable to invoke the assertion enablement method", e );
768             }
769         }
770         for ( Iterator iter = urls.iterator(); iter.hasNext(); )
771         {
772             URL url = (URL) iter.next();
773             classLoader.addURL( url );
774         }
775         return classLoader;
776     }
777 
778     private static List processStringList( String stringList )
779     {
780         String sl = stringList;
781 
782         if ( sl.startsWith( "[" ) && sl.endsWith( "]" ) )
783         {
784             sl = sl.substring( 1, sl.length() - 1 );
785         }
786 
787         List list = new ArrayList();
788 
789         String[] stringArray = StringUtils.split( sl, "," );
790 
791         for ( int i = 0; i < stringArray.length; i++ )
792         {
793             list.add( stringArray[i].trim() );
794         }
795         return list;
796     }
797 
798     private static Properties loadProperties( File file )
799         throws IOException
800     {
801         Properties p = new Properties();
802 
803         if ( file != null && file.exists() )
804         {
805             FileInputStream inStream = new FileInputStream( file );
806             try
807             {
808                 p.load( inStream );
809             }
810             finally
811             {
812                 IOUtil.close( inStream );
813             }
814         }
815 
816         return p;
817     }
818 
819     private static void setSystemProperties( File file )
820         throws IOException
821     {
822         Properties p = loadProperties( file );
823 
824         for ( Iterator i = p.keySet().iterator(); i.hasNext(); )
825         {
826             String key = (String) i.next();
827 
828             System.setProperty( key, p.getProperty( key ) );
829         }
830     }
831 
832     private static Object[] constructParamObjects( String paramProperty, String typeProperty )
833     {
834         Object[] paramObjects = null;
835         if ( paramProperty != null )
836         {
837             // bit of a glitch that it need sto be done twice to do an odd number of vertical bars (eg |||, |||||).
838             String[] params =
839                 StringUtils.split( StringUtils.replace( StringUtils.replace( paramProperty, "||", "| |" ),
840                                                         "||", "| |" ), "|" );
841             String[] types =
842                 StringUtils.split( StringUtils.replace( StringUtils.replace( typeProperty, "||", "| |" ),
843                                                         "||", "| |" ), "|" );
844 
845             paramObjects = new Object[params.length];
846 
847             for ( int i = 0; i < types.length; i++ )
848             {
849                 if ( types[i].trim().length() == 0 )
850                 {
851                     params[i] = null;
852                 }
853                 else if ( types[i].equals( String.class.getName() ) )
854                 {
855                     paramObjects[i] = params[i];
856                 }
857                 else if ( types[i].equals( File.class.getName() ) )
858                 {
859                     paramObjects[i] = new File( params[i] );
860                 }
861                 else if ( types[i].equals( File[].class.getName() ) )
862                 {
863                     List stringList = processStringList( params[i] );
864                     File[] fileList = new File[stringList.size()];
865                     for ( int j = 0; j < stringList.size(); j++ )
866                     {
867                         fileList[j] = new File( (String) stringList.get( j ) );
868                     }
869                     paramObjects[i] = fileList;
870                 }
871                 else if ( types[i].equals( ArrayList.class.getName() ) )
872                 {
873                     paramObjects[i] = processStringList( params[i] );
874                 }
875                 else if ( types[i].equals( Boolean.class.getName() ) )
876                 {
877                     paramObjects[i] = Boolean.valueOf( params[i] );
878                 }
879                 else if ( types[i].equals( Integer.class.getName() ) )
880                 {
881                     paramObjects[i] = Integer.valueOf( params[i] );
882                 }
883                 else if ( types[i].equals( Properties.class.getName() ) )
884                 {
885                     final Properties result = new Properties();
886                     final String value = params[i];
887                     try
888                     {
889                         ByteArrayInputStream bais = new ByteArrayInputStream( value.getBytes( "8859_1" ) );
890                         result.load( bais );
891                     }
892                     catch ( Exception e )
893                     {
894                         throw new RuntimeException( "bug in property conversion", e );
895                     }
896                     paramObjects[i] = result;
897                 }
898                 else
899                 {
900                     // TODO: could attempt to construct with a String constructor if needed
901                     throw new IllegalArgumentException( "Unknown parameter type: " + types[i] );
902                 }
903             }
904         }
905         return paramObjects;
906     }
907 
908     /**
909      * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
910      * then calls the Surefire class' run method. <p/> The system exit code will be 1 if an exception is thrown.
911      *
912      * @param args
913      */
914     public static void main( String[] args )
915         throws Throwable
916     {
917         // noinspection CatchGenericClass,OverlyBroadCatchBlock
918         try
919         {
920             if ( args.length > 1 )
921             {
922                 setSystemProperties( new File( args[1] ) );
923             }
924 
925             File surefirePropertiesFile = new File( args[0] );
926             Properties p = loadProperties( surefirePropertiesFile );
927 
928             SortedMap classPathUrls = new TreeMap();
929 
930             SortedMap surefireClassPathUrls = new TreeMap();
931 
932             SurefireBooter surefireBooter = new SurefireBooter( true );
933 
934             ForkConfiguration forkConfiguration = new ForkConfiguration();
935             forkConfiguration.setForkMode( "never" );
936             surefireBooter.setForkConfiguration( forkConfiguration );
937 
938             for ( Enumeration e = p.propertyNames(); e.hasMoreElements(); )
939             {
940                 String name = (String) e.nextElement();
941 
942                 if ( name.startsWith( REPORT_PROPERTY_PREFIX ) && !name.endsWith( PARAMS_SUFIX )
943                                 && !name.endsWith( TYPES_SUFIX ) )
944                 {
945                     String className = p.getProperty( name );
946 
947                     String params = p.getProperty( name + PARAMS_SUFIX );
948                     String types = p.getProperty( name + TYPES_SUFIX );
949                     surefireBooter.addReport( className, constructParamObjects( params, types ) );
950                 }
951                 else if ( name.startsWith( TEST_SUITE_PROPERTY_PREFIX ) && !name.endsWith( PARAMS_SUFIX )
952                                 && !name.endsWith( TYPES_SUFIX ) )
953                 {
954                     String className = p.getProperty( name );
955 
956                     String params = p.getProperty( name + PARAMS_SUFIX );
957                     String types = p.getProperty( name + TYPES_SUFIX );
958                     surefireBooter.addTestSuite( className, constructParamObjects( params, types ) );
959                 }
960                 else if ( name.startsWith( "classPathUrl." ) )
961                 {
962                     classPathUrls.put( Integer.valueOf( name.substring( name.indexOf( '.' ) + 1 ) ),
963                                        p.getProperty( name ) );
964                 }
965                 else if ( name.startsWith( "surefireClassPathUrl." ) )
966                 {
967                     surefireClassPathUrls.put( Integer.valueOf( name.substring( name.indexOf( '.' ) + 1 ) ),
968                                                p.getProperty( name ) );
969                 }
970                 else if ( name.startsWith( "surefireBootClassPathUrl." ) )
971                 {
972                     surefireBooter.addSurefireBootClassPathUrl( p.getProperty( name ) );
973                 }
974                 else if ( "childDelegation".equals( name ) )
975                 {
976                     surefireBooter.childDelegation =
977                         Boolean.valueOf( p.getProperty( "childDelegation" ) ).booleanValue();
978                 }
979                 else if ( "enableAssertions".equals( name ) )
980                 {
981                     surefireBooter.enableAssertions =
982                         Boolean.valueOf( p.getProperty( "enableAssertions" ) ).booleanValue();
983                 }
984                 else if ( "useSystemClassLoader".equals( name ) )
985                 {
986                     boolean value = Boolean.valueOf( p.getProperty( "useSystemClassLoader" ) ).booleanValue();
987                     surefireBooter.forkConfiguration.setUseSystemClassLoader( value );
988                 }
989                 else if ( "useManifestOnlyJar".equals( name ) )
990                 {
991                     boolean value = Boolean.valueOf( p.getProperty( "useManifestOnlyJar" ) ).booleanValue();
992                     surefireBooter.forkConfiguration.setUseManifestOnlyJar( value );
993                 }
994                 else if ( "failIfNoTests".equals( name ) )
995                 {
996                     boolean value = Boolean.valueOf( p.getProperty( "failIfNoTests" ) ).booleanValue();
997                     surefireBooter.setFailIfNoTests( value );
998                 }
999             }
1000 
1001             for ( Iterator cpi = classPathUrls.keySet().iterator(); cpi.hasNext(); )
1002             {
1003                 String url = (String) classPathUrls.get( cpi.next() );
1004                 surefireBooter.addClassPathUrl( url );
1005             }
1006 
1007             for ( Iterator scpi = surefireClassPathUrls.keySet().iterator(); scpi.hasNext(); )
1008             {
1009                 String url = (String) surefireClassPathUrls.get( scpi.next() );
1010                 surefireBooter.addSurefireClassPathUrl( url );
1011             }
1012 
1013             String testSet = p.getProperty( "testSet" );
1014             int result;
1015             if ( testSet != null )
1016             {
1017                 result = surefireBooter.runSuitesInProcess( testSet, p );
1018             }
1019             else
1020             {
1021                 result = surefireBooter.runSuitesInProcess();
1022             }
1023 
1024             surefireBooter.writePropertiesFile( surefirePropertiesFile, "surefire", p );
1025 
1026             // noinspection CallToSystemExit
1027             System.exit( result );
1028         }
1029         catch ( Throwable t )
1030         {
1031             // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace
1032             // noinspection UseOfSystemOutOrSystemErr
1033             t.printStackTrace( System.err );
1034             // noinspection ProhibitedExceptionThrown,CallToSystemExit
1035             System.exit( 1 );
1036         }
1037     }
1038 
1039     public void setChildDelegation( boolean childDelegation )
1040     {
1041         this.childDelegation = childDelegation;
1042     }
1043 
1044     private ForkingStreamConsumer getForkingStreamConsumer( boolean showHeading, boolean showFooter,
1045                                                      boolean redirectTestOutputToFile )
1046     {
1047         OutputConsumer outputConsumer = new StandardOutputConsumer();
1048 
1049         if ( redirectTestOutputToFile )
1050         {
1051             outputConsumer = new FileOutputConsumerProxy( outputConsumer, getReportsDirectory() );
1052         }
1053 
1054         if ( !showHeading )
1055         {
1056             outputConsumer = new SupressHeaderOutputConsumerProxy( outputConsumer );
1057         }
1058         if ( !showFooter )
1059         {
1060             outputConsumer = new SupressFooterOutputConsumerProxy( outputConsumer );
1061         }
1062 
1063         return new ForkingStreamConsumer( outputConsumer );
1064     }
1065 
1066     public void setEnableAssertions( boolean enableAssertions )
1067     {
1068         this.enableAssertions = enableAssertions;
1069     }
1070     
1071     public void setForkedProcessTimeoutInSeconds( int forkedProcessTimeoutInSeconds )
1072     {
1073         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
1074     }
1075 }