Coverage Report - org.apache.maven.plugin.surefire.booterclient.ForkStarter
 
Classes in this File Line Coverage Branch Coverage Complexity
ForkStarter
0%
0/143
0%
0/54
5,077
ForkStarter$1
0%
0/4
N/A
5,077
ForkStarter$2
0%
0/3
N/A
5,077
ForkStarter$InputStreamCloser
0%
0/9
0%
0/2
5,077
 
 1  
 package org.apache.maven.plugin.surefire.booterclient;
 2  
 
 3  
 /*
 4  
  * Licensed to the Apache Software Foundation (ASF) under one
 5  
  * or more contributor license agreements.  See the NOTICE file
 6  
  * distributed with this work for additional information
 7  
  * regarding copyright ownership.  The ASF licenses this file
 8  
  * to you under the Apache License, Version 2.0 (the
 9  
  * "License"); you may not use this file except in compliance
 10  
  * with the License.  You may obtain a copy of the License at
 11  
  *
 12  
  *     http://www.apache.org/licenses/LICENSE-2.0
 13  
  *
 14  
  * Unless required by applicable law or agreed to in writing,
 15  
  * software distributed under the License is distributed on an
 16  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 17  
  * KIND, either express or implied.  See the License for the
 18  
  * specific language governing permissions and limitations
 19  
  * under the License.
 20  
  */
 21  
 
 22  
 import java.io.File;
 23  
 import java.io.IOException;
 24  
 import java.io.InputStream;
 25  
 import java.util.ArrayList;
 26  
 import java.util.Iterator;
 27  
 import java.util.List;
 28  
 import java.util.Properties;
 29  
 import java.util.Queue;
 30  
 import java.util.concurrent.ArrayBlockingQueue;
 31  
 import java.util.concurrent.Callable;
 32  
 import java.util.concurrent.ConcurrentLinkedQueue;
 33  
 import java.util.concurrent.ExecutionException;
 34  
 import java.util.concurrent.ExecutorService;
 35  
 import java.util.concurrent.Future;
 36  
 import java.util.concurrent.LinkedBlockingQueue;
 37  
 import java.util.concurrent.ThreadPoolExecutor;
 38  
 import java.util.concurrent.TimeUnit;
 39  
 
 40  
 import org.apache.maven.plugin.logging.Log;
 41  
 import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
 42  
 import org.apache.maven.plugin.surefire.CommonReflector;
 43  
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
 44  
 import org.apache.maven.plugin.surefire.SurefireProperties;
 45  
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
 46  
 import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream;
 47  
 import org.apache.maven.plugin.surefire.booterclient.output.ForkClient;
 48  
 import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer;
 49  
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 50  
 import org.apache.maven.shared.utils.cli.CommandLineException;
 51  
 import org.apache.maven.shared.utils.cli.CommandLineTimeOutException;
 52  
 import org.apache.maven.shared.utils.cli.CommandLineUtils;
 53  
 import org.apache.maven.shared.utils.cli.ShutdownHookUtils;
 54  
 import org.apache.maven.surefire.booter.Classpath;
 55  
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
 56  
 import org.apache.maven.surefire.booter.KeyValueSource;
 57  
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 58  
 import org.apache.maven.surefire.booter.ProviderConfiguration;
 59  
 import org.apache.maven.surefire.booter.ProviderFactory;
 60  
 import org.apache.maven.surefire.booter.StartupConfiguration;
 61  
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
 62  
 import org.apache.maven.surefire.booter.SurefireExecutionException;
 63  
 import org.apache.maven.surefire.booter.SystemPropertyManager;
 64  
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 65  
 import org.apache.maven.surefire.report.StackTraceWriter;
 66  
 import org.apache.maven.surefire.suite.RunResult;
 67  
 import org.apache.maven.surefire.util.DefaultScanResult;
 68  
 
 69  
 import static org.apache.maven.surefire.booter.Classpath.join;
 70  
 
 71  
 /**
 72  
  * Starts the fork or runs in-process.
 73  
  * <p/>
 74  
  * Lives only on the plugin-side (not present in remote vms)
 75  
  * <p/>
 76  
  * Knows how to fork new vms and also how to delegate non-forking invocation to SurefireStarter directly
 77  
  *
 78  
  * @author Jason van Zyl
 79  
  * @author Emmanuel Venisse
 80  
  * @author Brett Porter
 81  
  * @author Dan Fabulich
 82  
  * @author Carlos Sanchez
 83  
  * @author Kristian Rosenvold
 84  
  */
 85  0
 public class ForkStarter
 86  
 {
 87  
     /**
 88  
      * Closes an InputStream
 89  
      */
 90  
     private final class InputStreamCloser
 91  
         implements Runnable
 92  
     {
 93  
         private InputStream testProvidingInputStream;
 94  
 
 95  
         public InputStreamCloser( InputStream testProvidingInputStream )
 96  0
         {
 97  0
             this.testProvidingInputStream = testProvidingInputStream;
 98  0
         }
 99  
 
 100  
         public synchronized void run()
 101  
         {
 102  0
             if ( testProvidingInputStream != null )
 103  
             {
 104  
                 try
 105  
                 {
 106  0
                     testProvidingInputStream.close();
 107  
                 }
 108  0
                 catch ( IOException e )
 109  
                 {
 110  
                     // ignore
 111  0
                 }
 112  0
                 testProvidingInputStream = null;
 113  
             }
 114  0
         }
 115  
     }
 116  
 
 117  
     private final int forkedProcessTimeoutInSeconds;
 118  
 
 119  
     private final ProviderConfiguration providerConfiguration;
 120  
 
 121  
     private final StartupConfiguration startupConfiguration;
 122  
 
 123  
     private final ForkConfiguration forkConfiguration;
 124  
 
 125  
     private final StartupReportConfiguration startupReportConfiguration;
 126  
 
 127  
     private Log log;
 128  
 
 129  
     private final DefaultReporterFactory defaultReporterFactory;
 130  
 
 131  0
     private static volatile int systemPropertiesFileCounter = 0;
 132  
 
 133  
     public ForkStarter( ProviderConfiguration providerConfiguration, StartupConfiguration startupConfiguration,
 134  
                         ForkConfiguration forkConfiguration, int forkedProcessTimeoutInSeconds,
 135  
                         StartupReportConfiguration startupReportConfiguration, Log log )
 136  0
     {
 137  0
         this.forkConfiguration = forkConfiguration;
 138  0
         this.providerConfiguration = providerConfiguration;
 139  0
         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
 140  0
         this.startupConfiguration = startupConfiguration;
 141  0
         this.startupReportConfiguration = startupReportConfiguration;
 142  0
         this.log = log;
 143  0
         defaultReporterFactory = new DefaultReporterFactory( startupReportConfiguration );
 144  0
     }
 145  
 
 146  
     public RunResult run( SurefireProperties effectiveSystemProperties, DefaultScanResult scanResult )
 147  
         throws SurefireBooterForkException, SurefireExecutionException
 148  
     {
 149  
         final RunResult result;
 150  
         try
 151  
         {
 152  0
             Properties providerProperties = providerConfiguration.getProviderProperties();
 153  0
             scanResult.writeTo( providerProperties );
 154  0
             if ( isForkOnce() )
 155  
             {
 156  0
                 final ForkClient forkClient =
 157  
                     new ForkClient( defaultReporterFactory, startupReportConfiguration.getTestVmSystemProperties() );
 158  0
                 result = fork( null, new PropertiesWrapper( providerProperties ), forkClient, effectiveSystemProperties,
 159  
                                null );
 160  0
             }
 161  
             else
 162  
             {
 163  0
                 if ( forkConfiguration.isReuseForks() )
 164  
                 {
 165  0
                     result = runSuitesForkOnceMultiple( effectiveSystemProperties, forkConfiguration.getForkCount() );
 166  
                 }
 167  
                 else
 168  
                 {
 169  0
                     result = runSuitesForkPerTestSet( effectiveSystemProperties, forkConfiguration.getForkCount() );
 170  
                 }
 171  
             }
 172  
         }
 173  
         finally
 174  
         {
 175  0
             defaultReporterFactory.close();
 176  0
         }
 177  0
         return result;
 178  
     }
 179  
 
 180  
     private boolean isForkOnce()
 181  
     {
 182  0
         return forkConfiguration.isReuseForks() && 1 == forkConfiguration.getForkCount();
 183  
     }
 184  
 
 185  
     private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount )
 186  
         throws SurefireBooterForkException
 187  
     {
 188  
 
 189  0
         ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount );
 190  0
         ExecutorService executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS,
 191  
                                                                   new ArrayBlockingQueue<Runnable>( forkCount ) );
 192  
 
 193  
         try
 194  
         {
 195  
             // Ask to the executorService to run all tasks
 196  0
             RunResult globalResult = new RunResult( 0, 0, 0, 0 );
 197  
 
 198  0
             List<Class<?>> suites = new ArrayList<Class<?>>();
 199  0
             Iterator<Class<?>> suitesIterator = getSuitesIterator();
 200  0
             while ( suitesIterator.hasNext() )
 201  
             {
 202  0
                 suites.add( suitesIterator.next() );
 203  
             }
 204  0
             final Queue<String> messageQueue = new ConcurrentLinkedQueue<String>();
 205  0
             for ( Class<?> clazz : suites )
 206  
             {
 207  0
                 messageQueue.add( clazz.getName() );
 208  
             }
 209  
 
 210  0
             for ( int forkNum = 0; forkNum < forkCount && forkNum < suites.size(); forkNum++ )
 211  
             {
 212  0
                 Callable<RunResult> pf = new Callable<RunResult>()
 213  
                 {
 214  0
                     public RunResult call()
 215  
                         throws Exception
 216  
                     {
 217  0
                         TestProvidingInputStream testProvidingInputStream =
 218  
                             new TestProvidingInputStream( messageQueue );
 219  
 
 220  0
                         ForkClient forkClient = new ForkClient( defaultReporterFactory,
 221  
                                                                 startupReportConfiguration.getTestVmSystemProperties(),
 222  
                                                                 testProvidingInputStream );
 223  
 
 224  0
                         return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
 225  
                                      forkClient, effectiveSystemProperties, testProvidingInputStream );
 226  
                     }
 227  
                 };
 228  
 
 229  0
                 results.add( executorService.submit( pf ) );
 230  
             }
 231  
 
 232  0
             for ( Future<RunResult> result : results )
 233  
             {
 234  
                 try
 235  
                 {
 236  0
                     RunResult cur = result.get();
 237  0
                     if ( cur != null )
 238  
                     {
 239  0
                         globalResult = globalResult.aggregate( cur );
 240  
                     }
 241  
                     else
 242  
                     {
 243  0
                         throw new SurefireBooterForkException( "No results for " + result.toString() );
 244  
                     }
 245  
                 }
 246  0
                 catch ( InterruptedException e )
 247  
                 {
 248  0
                     throw new SurefireBooterForkException( "Interrupted", e );
 249  
                 }
 250  0
                 catch ( ExecutionException e )
 251  
                 {
 252  0
                     throw new SurefireBooterForkException( "ExecutionException", e );
 253  0
                 }
 254  
             }
 255  0
             return globalResult;
 256  
 
 257  
         }
 258  
         finally
 259  
         {
 260  0
             closeExecutor( executorService );
 261  
         }
 262  
 
 263  
     }
 264  
 
 265  
     private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, final int forkCount )
 266  
         throws SurefireBooterForkException
 267  
     {
 268  
 
 269  0
         ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( 500 );
 270  0
         ExecutorService executorService =
 271  
             new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() );
 272  
 
 273  
         try
 274  
         {
 275  
             // Ask to the executorService to run all tasks
 276  0
             RunResult globalResult = new RunResult( 0, 0, 0, 0 );
 277  0
             final Iterator<Class<?>> suites = getSuitesIterator();
 278  0
             while ( suites.hasNext() )
 279  
             {
 280  0
                 final Object testSet = suites.next();
 281  0
                 Callable<RunResult> pf = new Callable<RunResult>()
 282  
                 {
 283  0
                     public RunResult call()
 284  
                         throws Exception
 285  
                     {
 286  0
                         ForkClient forkClient = new ForkClient( defaultReporterFactory,
 287  
                                                                 startupReportConfiguration.getTestVmSystemProperties() );
 288  0
                         return fork( testSet, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
 289  
                                      forkClient, effectiveSystemProperties, null );
 290  
                     }
 291  
                 };
 292  0
                 results.add( executorService.submit( pf ) );
 293  
 
 294  0
             }
 295  
 
 296  0
             for ( Future<RunResult> result : results )
 297  
             {
 298  
                 try
 299  
                 {
 300  0
                     RunResult cur = result.get();
 301  0
                     if ( cur != null )
 302  
                     {
 303  0
                         globalResult = globalResult.aggregate( cur );
 304  
                     }
 305  
                     else
 306  
                     {
 307  0
                         throw new SurefireBooterForkException( "No results for " + result.toString() );
 308  
                     }
 309  
                 }
 310  0
                 catch ( InterruptedException e )
 311  
                 {
 312  0
                     throw new SurefireBooterForkException( "Interrupted", e );
 313  
                 }
 314  0
                 catch ( ExecutionException e )
 315  
                 {
 316  0
                     throw new SurefireBooterForkException( "ExecutionException", e );
 317  0
                 }
 318  
             }
 319  0
             return globalResult;
 320  
 
 321  
         }
 322  
         finally
 323  
         {
 324  0
             closeExecutor( executorService );
 325  
         }
 326  
 
 327  
     }
 328  
 
 329  
     private void closeExecutor( ExecutorService executorService )
 330  
         throws SurefireBooterForkException
 331  
     {
 332  0
         executorService.shutdown();
 333  
         try
 334  
         {
 335  
             // Should stop immediately, as we got all the results if we are here
 336  0
             executorService.awaitTermination( 60 * 60, TimeUnit.SECONDS );
 337  
         }
 338  0
         catch ( InterruptedException e )
 339  
         {
 340  0
             throw new SurefireBooterForkException( "Interrupted", e );
 341  0
         }
 342  0
     }
 343  
 
 344  
     private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
 345  
                             SurefireProperties effectiveSystemProperties,
 346  
                             TestProvidingInputStream testProvidingInputStream )
 347  
         throws SurefireBooterForkException
 348  
     {
 349  0
         int forkNumber = ForkNumberBucket.drawNumber();
 350  
         try
 351  
         {
 352  0
             return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber,
 353  
                          testProvidingInputStream );
 354  
         }
 355  
         finally
 356  
         {
 357  0
             ForkNumberBucket.returnNumber( forkNumber );
 358  
         }
 359  
     }
 360  
 
 361  
     private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
 362  
                             SurefireProperties effectiveSystemProperties, int forkNumber,
 363  
                             TestProvidingInputStream testProvidingInputStream )
 364  
         throws SurefireBooterForkException
 365  
     {
 366  
         File surefireProperties;
 367  0
         File systPropsFile = null;
 368  
         try
 369  
         {
 370  0
             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
 371  
 
 372  0
             surefireProperties =
 373  
                 booterSerializer.serialize( providerProperties, providerConfiguration, startupConfiguration, testSet,
 374  
                                             null != testProvidingInputStream );
 375  
 
 376  0
             if ( effectiveSystemProperties != null )
 377  
             {
 378  0
                 SurefireProperties filteredProperties =
 379  
                     AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties,
 380  
                                                                                  forkNumber );
 381  0
                 systPropsFile =
 382  
                     SystemPropertyManager.writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(),
 383  
                                                                "surefire_" + systemPropertiesFileCounter++,
 384  
                                                                forkConfiguration.isDebug() );
 385  
             }
 386  
         }
 387  0
         catch ( IOException e )
 388  
         {
 389  0
             throw new SurefireBooterForkException( "Error creating properties files for forking", e );
 390  0
         }
 391  
 
 392  
         // this could probably be simplified further
 393  0
         final Classpath bootClasspathConfiguration = startupConfiguration.isProviderMainClass()
 394  
             ? startupConfiguration.getClasspathConfiguration().getProviderClasspath()
 395  
             : forkConfiguration.getBootClasspath();
 396  
 
 397  0
         Classpath bootClasspath = join(
 398  
             join( bootClasspathConfiguration, startupConfiguration.getClasspathConfiguration().getTestClasspath() ),
 399  
             startupConfiguration.getClasspathConfiguration().getProviderClasspath() );
 400  
 
 401  0
         if ( log.isDebugEnabled() )
 402  
         {
 403  0
             log.debug( bootClasspath.getLogMessage( "boot" ) );
 404  0
             log.debug( bootClasspath.getCompactLogMessage( "boot(compact)" ) );
 405  
         }
 406  0
         OutputStreamFlushableCommandline cli =
 407  
             forkConfiguration.createCommandLine( bootClasspath.getClassPath(), startupConfiguration, forkNumber );
 408  
 
 409  
         final InputStreamCloser inputStreamCloser;
 410  
         final Thread inputStreamCloserHook;
 411  0
         if ( testProvidingInputStream != null )
 412  
         {
 413  0
             testProvidingInputStream.setFlushReceiverProvider( cli );
 414  0
             inputStreamCloser = new InputStreamCloser( testProvidingInputStream );
 415  0
             inputStreamCloserHook = new Thread( inputStreamCloser );
 416  0
             ShutdownHookUtils.addShutDownHook( inputStreamCloserHook );
 417  
         }
 418  
         else
 419  
         {
 420  0
             inputStreamCloser = null;
 421  0
             inputStreamCloserHook = null;
 422  
         }
 423  
 
 424  0
         cli.createArg().setFile( surefireProperties );
 425  
 
 426  0
         if ( systPropsFile != null )
 427  
         {
 428  0
             cli.createArg().setFile( systPropsFile );
 429  
         }
 430  
 
 431  0
         ThreadedStreamConsumer threadedStreamConsumer = new ThreadedStreamConsumer( forkClient );
 432  
 
 433  0
         if ( forkConfiguration.isDebug() )
 434  
         {
 435  0
             System.out.println( "Forking command line: " + cli );
 436  
         }
 437  
 
 438  0
         RunResult runResult = null;
 439  
 
 440  
         try
 441  
         {
 442  0
             final int timeout = forkedProcessTimeoutInSeconds > 0 ? forkedProcessTimeoutInSeconds : 0;
 443  0
             final int result =
 444  
                 CommandLineUtils.executeCommandLine( cli, testProvidingInputStream, threadedStreamConsumer,
 445  
                                                      threadedStreamConsumer, timeout, inputStreamCloser );
 446  0
             if ( result != RunResult.SUCCESS )
 447  
             {
 448  0
                 throw new SurefireBooterForkException( "Error occurred in starting fork, check output in log" );
 449  
             }
 450  
 
 451  0
         }
 452  0
         catch ( CommandLineTimeOutException e )
 453  
         {
 454  0
             runResult = RunResult.timeout( defaultReporterFactory.getGlobalRunStatistics().getRunResult() );
 455  0
         }
 456  0
         catch ( CommandLineException e )
 457  
         {
 458  0
             runResult = RunResult.failure( defaultReporterFactory.getGlobalRunStatistics().getRunResult(), e );
 459  0
             throw new SurefireBooterForkException( "Error while executing forked tests.", e.getCause() );
 460  
         }
 461  
         finally
 462  
         {
 463  0
             threadedStreamConsumer.close();
 464  0
             if ( inputStreamCloser != null )
 465  
             {
 466  0
                 inputStreamCloser.run();
 467  0
                 ShutdownHookUtils.removeShutdownHook( inputStreamCloserHook );
 468  
             }
 469  0
             if ( runResult == null )
 470  
             {
 471  0
                 runResult = defaultReporterFactory.getGlobalRunStatistics().getRunResult();
 472  
             }
 473  0
             if ( !runResult.isTimeout() )
 474  
             {
 475  0
                 StackTraceWriter errorInFork = forkClient.getErrorInFork();
 476  0
                 if ( errorInFork != null )
 477  
                 {
 478  
                     // noinspection ThrowFromFinallyBlock
 479  0
                     throw new RuntimeException(
 480  
                         "There was an error in the forked process\n" + errorInFork.writeTraceToString() );
 481  
                 }
 482  0
                 if ( !forkClient.isSaidGoodBye() )
 483  
                 {
 484  
                     // noinspection ThrowFromFinallyBlock
 485  0
                     throw new RuntimeException(
 486  
                         "The forked VM terminated without saying properly goodbye. VM crash or System.exit called ?"
 487  
                             + "\nCommand was" + cli.toString() );
 488  
                 }
 489  
 
 490  
             }
 491  0
             forkClient.close( runResult.isTimeout() );
 492  0
         }
 493  
 
 494  0
         return runResult;
 495  
     }
 496  
 
 497  
     @SuppressWarnings( "unchecked" )
 498  
     private Iterator<Class<?>> getSuitesIterator()
 499  
         throws SurefireBooterForkException
 500  
     {
 501  
         try
 502  
         {
 503  0
             final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
 504  0
             ClassLoader unifiedClassLoader = classpathConfiguration.createMergedClassLoader();
 505  
 
 506  0
             CommonReflector commonReflector = new CommonReflector( unifiedClassLoader );
 507  0
             Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration );
 508  
 
 509  0
             final ProviderFactory providerFactory =
 510  
                 new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader,
 511  
                                      reporterFactory );
 512  0
             SurefireProvider surefireProvider = providerFactory.createProvider( false );
 513  0
             return surefireProvider.getSuites();
 514  
         }
 515  0
         catch ( SurefireExecutionException e )
 516  
         {
 517  0
             throw new SurefireBooterForkException( "Unable to create classloader to find test suites", e );
 518  
         }
 519  
     }
 520  
 }