maven-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tibordig...@apache.org
Subject [2/2] maven-surefire git commit: [SUREFIRE-1302] Surefire does not wait long enough for the forked VM and assumes it to be dead
Date Mon, 24 Jul 2017 02:59:26 GMT
[SUREFIRE-1302] Surefire does not wait long enough for the forked VM and assumes it to be dead


Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo
Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/5cbdc7a4
Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/5cbdc7a4
Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/5cbdc7a4

Branch: refs/heads/SUREFIRE-1302_4
Commit: 5cbdc7a4c12d77f42270735a742038581c6bac4a
Parents: 4de017b
Author: Tibor17 <tibordigana@apache.org>
Authored: Wed Jul 19 01:21:41 2017 +0200
Committer: Tibor17 <tibordigana@apache.org>
Committed: Mon Jul 24 04:58:50 2017 +0200

----------------------------------------------------------------------
 maven-failsafe-plugin/pom.xml                   |  17 -
 maven-surefire-common/pom.xml                   |  11 +-
 .../plugin/surefire/AbstractSurefireMojo.java   |  14 +-
 .../plugin/surefire/SurefireProperties.java     |   8 +
 .../surefire/booterclient/BooterSerializer.java |   6 +-
 .../booterclient/ForkConfiguration.java         |  10 +-
 .../surefire/booterclient/ForkStarter.java      |   6 +-
 .../plugin/surefire/booterclient/Platform.java  |  70 +++
 .../surefire/report/FileReporterUtils.java      |  11 +-
 ...erDeserializerProviderConfigurationTest.java |   3 +-
 ...terDeserializerStartupConfigurationTest.java |   3 +-
 .../booterclient/ForkConfigurationTest.java     |   2 +-
 .../report/DefaultReporterFactoryTest.java      |   2 +
 maven-surefire-plugin/pom.xml                   |  17 -
 .../src/site/apt/examples/shutdown.apt.vm       |  26 +-
 maven-surefire-report-plugin/pom.xml            |   4 -
 pom.xml                                         |  19 +-
 surefire-api/pom.xml                            |  11 +-
 .../maven/surefire/booter/CommandReader.java    |   4 +-
 .../maven/surefire/util/ReflectionUtils.java    |  61 ++-
 .../surefire/util/internal/SystemUtils.java     |  99 -----
 .../java/org/apache/maven/JUnit4SuiteTest.java  |   6 +-
 .../surefire/util/ReflectionUtilsTest.java      | 114 +++++
 .../surefire/util/internal/SystemUtilsTest.java |  98 -----
 surefire-booter/pom.xml                         |  47 +-
 .../maven/surefire/booter/BooterConstants.java  |   1 +
 .../surefire/booter/BooterDeserializer.java     |   8 +
 .../apache/maven/surefire/booter/Classpath.java |   2 -
 .../maven/surefire/booter/ForkedBooter.java     | 426 ++++++++++---------
 .../maven/surefire/booter/PpidChecker.java      | 295 +++++++++++++
 .../maven/surefire/booter/ProcessInfo.java      | 109 +++++
 .../surefire/booter/PropertiesWrapper.java      |   6 +
 .../maven/surefire/booter/SystemUtils.java      | 181 ++++++++
 .../maven/surefire/booter/JUnit4SuiteTest.java  |   4 +-
 .../surefire/booter/NewClassLoaderRunner.java   |  68 ++-
 .../maven/surefire/booter/PpidCheckerTest.java  | 144 +++++++
 .../maven/surefire/booter/SystemUtilsTest.java  | 131 ++++++
 ...urefire1295AttributeJvmCrashesToTestsIT.java |   6 +-
 38 files changed, 1564 insertions(+), 486 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-failsafe-plugin/pom.xml
----------------------------------------------------------------------
diff --git a/maven-failsafe-plugin/pom.xml b/maven-failsafe-plugin/pom.xml
index f42e682..ec48929 100644
--- a/maven-failsafe-plugin/pom.xml
+++ b/maven-failsafe-plugin/pom.xml
@@ -45,27 +45,10 @@
 
   <dependencies>
     <dependency>
-      <groupId>org.apache.maven</groupId>
-      <artifactId>maven-plugin-api</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.apache.maven.surefire</groupId>
       <artifactId>maven-surefire-common</artifactId>
     </dependency>
     <dependency>
-      <groupId>org.apache.maven.surefire</groupId>
-      <artifactId>surefire-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven.shared</groupId>
-      <artifactId>maven-shared-utils</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven.plugin-tools</groupId>
-      <artifactId>maven-plugin-annotations</artifactId>
-      <scope>compile</scope>
-    </dependency>
-    <dependency>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-surefire-plugin</artifactId>
       <version>${project.version}</version>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/pom.xml
----------------------------------------------------------------------
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index 4064bc2..ae050c7 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -44,7 +44,6 @@
     <dependency>
       <groupId>org.apache.maven.plugin-tools</groupId>
       <artifactId>maven-plugin-annotations</artifactId>
-      <scope>compile</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.maven.surefire</groupId>
@@ -93,6 +92,7 @@
     <dependency>
       <groupId>com.google.code.findbugs</groupId>
       <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.maven.shared</groupId>
@@ -160,13 +160,11 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
+          <jvm>${test.jre}/bin/java</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>
-          <classpathDependencyExcludes>
-            <classpathDependencyExclude>org.fusesource.jansi:jansi</classpathDependencyExclude>
-          </classpathDependencyExcludes>
         </configuration>
         <dependencies>
           <dependency>
@@ -192,6 +190,7 @@
                   <include>org.apache.maven.shared:maven-shared-utils</include>
                   <include>org.apache.maven.shared:maven-common-artifact-filters</include>
                   <include>commons-io:commons-io</include>
+                  <include>org.apache.commons:commons-lang3</include>
                 </includes>
               </artifactSet>
               <relocations>
@@ -203,6 +202,10 @@
                   <pattern>org.apache.commons.io</pattern>
                   <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern>
                 </relocation>
+                <relocation>
+                  <pattern>org.apache.commons.lang3</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang3</shadedPattern>
+                </relocation>
               </relocations>
             </configuration>
           </execution>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index 551d980..b3df567 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -43,6 +43,7 @@ import org.apache.maven.plugin.descriptor.PluginDescriptor;
 import org.apache.maven.plugin.surefire.booterclient.ChecksumCalculator;
 import org.apache.maven.plugin.surefire.booterclient.ForkConfiguration;
 import org.apache.maven.plugin.surefire.booterclient.ForkStarter;
+import org.apache.maven.plugin.surefire.booterclient.Platform;
 import org.apache.maven.plugin.surefire.booterclient.ProviderDetector;
 import org.apache.maven.plugin.surefire.log.PluginConsoleLogger;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
@@ -114,6 +115,8 @@ public abstract class AbstractSurefireMojo
     extends AbstractMojo
     implements SurefireExecutionParameters
 {
+    private static final Platform PLATFORM = new Platform();
+
     private final ProviderDetector providerDetector = new ProviderDetector();
 
     /**
@@ -1623,13 +1626,14 @@ public abstract class AbstractSurefireMojo
             Classpath providerClasspath = ClasspathCache.getCachedClassPath( providerName );
             if ( providerClasspath == null )
             {
+                // todo: 100 milli seconds, try to fetch List<String> within classpath asynchronously
                 providerClasspath = provider.getProviderClasspath();
                 ClasspathCache.setCachedClasspath( providerName, providerClasspath );
             }
             Artifact surefireArtifact = getCommonArtifact();
-            Classpath inprocClassPath = providerClasspath.
-                    addClassPathElementUrl( surefireArtifact.getFile().getAbsolutePath() )
-                    .addClassPathElementUrl( getApiArtifact().getFile().getAbsolutePath() );
+            Classpath inprocClassPath =
+                    providerClasspath.addClassPathElementUrl( surefireArtifact.getFile().getAbsolutePath() )
+                            .addClassPathElementUrl( getApiArtifact().getFile().getAbsolutePath() );
 
             final Classpath testClasspath = generateTestClasspath();
 
@@ -1926,7 +1930,6 @@ public abstract class AbstractSurefireMojo
         ProviderConfiguration providerConfiguration = createProviderConfiguration( runOrderParameters );
         return new InPluginVMSurefireStarter( startupConfiguration, providerConfiguration,
                                                     startupReportConfiguration, consoleLogger );
-
     }
 
     protected ForkConfiguration getForkConfiguration()
@@ -1937,6 +1940,7 @@ public abstract class AbstractSurefireMojo
 
         Artifact shadeFire = getPluginArtifactMap().get( "org.apache.maven.surefire:surefire-shadefire" );
 
+        // todo: 150 milli seconds, try to fetch List<String> within classpath asynchronously
         final Classpath bootClasspathConfiguration =
             getArtifactClasspath( shadeFire != null ? shadeFire : surefireBooterArtifact );
 
@@ -1945,7 +1949,7 @@ public abstract class AbstractSurefireMojo
                                       getWorkingDirectory() != null ? getWorkingDirectory() : getBasedir(),
                                       getProject().getModel().getProperties(),
                                       getArgLine(), getEnvironmentVariables(), getConsoleLogger().isDebugEnabled(),
-                                      getEffectiveForkCount(), reuseForks );
+                                      getEffectiveForkCount(), reuseForks, PLATFORM );
     }
 
     private void convertDeprecatedForkMode()

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
index 4b13898..3783376 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java
@@ -196,6 +196,14 @@ public class SurefireProperties
         }
     }
 
+    public void setProperty( String key, Long value )
+    {
+        if ( value != null )
+        {
+            setProperty( key, value.toString() );
+        }
+    }
+
     public void addList( List<?> items, String propertyPrefix )
     {
         if ( items != null && !items.isEmpty() )

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
index 0299525..591e89c 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java
@@ -70,12 +70,14 @@ class BooterSerializer
      * Does not modify sourceProperties
      */
     File serialize( KeyValueSource sourceProperties, ProviderConfiguration booterConfiguration,
-                           StartupConfiguration providerConfiguration, Object testSet, boolean readTestsFromInStream )
+                    StartupConfiguration providerConfiguration, Object testSet, boolean readTestsFromInStream,
+                    Long pid )
         throws IOException
     {
-
         SurefireProperties properties = new SurefireProperties( sourceProperties );
 
+        properties.setProperty( PLUGIN_PID, pid );
+
         ClasspathConfiguration cp = providerConfiguration.getClasspathConfiguration();
         properties.setClasspath( ClasspathConfiguration.CLASSPATH, cp.getTestClasspath() );
         properties.setClasspath( ClasspathConfiguration.SUREFIRE_CLASSPATH, cp.getProviderClasspath() );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
index 2768210..1c7626e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
@@ -82,11 +82,13 @@ public class ForkConfiguration
 
     private final String debugLine;
 
+    private final Platform pluginPlatform;
+
     @SuppressWarnings( "checkstyle:parameternumber" )
     public ForkConfiguration( Classpath bootClasspathConfiguration, File tmpDir, String debugLine,
                               String jvmExecutable, File workingDirectory, Properties modelProperties, String argLine,
                               Map<String, String> environmentVariables, boolean debugEnabled, int forkCount,
-                              boolean reuseForks )
+                              boolean reuseForks, Platform pluginPlatform )
     {
         this.bootClasspathConfiguration = bootClasspathConfiguration;
         this.tempDirectory = tmpDir;
@@ -99,6 +101,7 @@ public class ForkConfiguration
         this.debug = debugEnabled;
         this.forkCount = forkCount;
         this.reuseForks = reuseForks;
+        this.pluginPlatform = pluginPlatform;
     }
 
     public Classpath getBootClasspath()
@@ -339,6 +342,11 @@ public class ForkConfiguration
         return reuseForks;
     }
 
+    public Platform getPluginPlatform()
+    {
+        return pluginPlatform;
+    }
+
     private static String stripNewLines( String argLine )
     {
         return argLine.replace( "\n", " " ).replace( "\r", " " );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index a2a5095..2d82855 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -550,7 +550,11 @@ public class ForkStarter
             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
 
             surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
-                                                             startupConfiguration, testSet, readTestsFromInStream );
+                                                                   startupConfiguration, testSet,
+                                                                   readTestsFromInStream,
+                                                                   forkConfiguration.getPluginPlatform().getPid() );
+
+            log.debug( "Determined Maven Process ID " + forkConfiguration.getPluginPlatform().getPid() );
 
             if ( effectiveSystemProperties != null )
             {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
new file mode 100644
index 0000000..10b16dc
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
@@ -0,0 +1,70 @@
+package org.apache.maven.plugin.surefire.booterclient;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.booter.SystemUtils;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.RunnableFuture;
+
+import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread;
+
+/**
+ * Loads platform specifics.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class Platform
+{
+    private final RunnableFuture<Long> pidJob;
+
+    public Platform()
+    {
+        // the job may take 50 or 80 ms
+        pidJob = new FutureTask<Long>( pidJob() );
+        newDaemonThread( pidJob ).start();
+    }
+
+    public Long getPid()
+    {
+        try
+        {
+            return pidJob.get();
+        }
+        catch ( Exception e )
+        {
+            return null;
+        }
+    }
+
+    private static Callable<Long> pidJob()
+    {
+        return new Callable<Long>()
+        {
+            @Override
+            public Long call() throws Exception
+            {
+                return SystemUtils.pid();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
index 36bc269..fd33d8e 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java
@@ -19,6 +19,8 @@ package org.apache.maven.plugin.surefire.report;
  * under the License.
  */
 
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+
 /**
  * Utils class for file-based reporters
  *
@@ -45,13 +47,6 @@ public final class FileReporterUtils
 
     private static String getOSSpecificIllegalChars()
     {
-        if ( System.getProperty( "os.name" ).toLowerCase().startsWith( "win" ) )
-        {
-            return "\\/:*?\"<>|\0";
-        }
-        else
-        {
-            return "/\0";
-        }
+        return IS_OS_WINDOWS ? "\\/:*?\"<>|\0" : "/\0";
     }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
index 6759367..5d970d8 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java
@@ -220,8 +220,9 @@ public class BooterDeserializerProviderConfigurationTest
             test = "aTest";
         }
         final File propsTest = booterSerializer.serialize( props, booterConfiguration, testProviderConfiguration, test,
-                                                           readTestsFromInStream );
+                                                           readTestsFromInStream, 51L );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
+        assertEquals( 51L, (Object) booterDeserializer.getPluginPid() );
         return booterDeserializer.deserialize();
     }
 

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
index 1ca20d2..0cb292c 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java
@@ -124,8 +124,9 @@ public class BooterDeserializerStartupConfigurationTest
         BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
         String aTest = "aTest";
         final File propsTest =
-            booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest, false );
+            booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest, false, null );
         BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) );
+        assertNull( booterDeserializer.getPluginPid() );
         return booterDeserializer.getProviderConfiguration();
     }
 

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
index 4f62670..1e09d6f 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java
@@ -162,7 +162,7 @@ public class ForkConfigurationTest
         throws IOException
     {
         return new ForkConfiguration( Classpath.emptyClasspath(), null, null, jvm, cwd, new Properties(), argLine, null,
-                                      false, 1, false );
+                                      false, 1, false, new Platform() );
     }
 
     // based on http://stackoverflow.com/questions/2591083/getting-version-of-java-in-runtime

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
index a6076de..67f66d6 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/report/DefaultReporterFactoryTest.java
@@ -27,6 +27,7 @@ import junit.framework.TestCase;
 
 import org.apache.maven.plugin.surefire.StartupReportConfiguration;
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.shared.utils.logging.MessageUtils;
 import org.apache.maven.surefire.report.RunStatistics;
 import org.apache.maven.surefire.report.SafeThrowable;
 import org.apache.maven.surefire.report.StackTraceWriter;
@@ -61,6 +62,7 @@ public class DefaultReporterFactoryTest
 
     public void testMergeTestHistoryResult()
     {
+        MessageUtils.setColorEnabled( false );
         File reportsDirectory = new File("target");
         StartupReportConfiguration reportConfig =
                 new StartupReportConfiguration( true, true, "PLAIN", false, false, reportsDirectory, false, null,

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-plugin/pom.xml
----------------------------------------------------------------------
diff --git a/maven-surefire-plugin/pom.xml b/maven-surefire-plugin/pom.xml
index 62ec4a7..2a186e3 100644
--- a/maven-surefire-plugin/pom.xml
+++ b/maven-surefire-plugin/pom.xml
@@ -45,26 +45,9 @@
 
   <dependencies>
     <dependency>
-      <groupId>org.apache.maven</groupId>
-      <artifactId>maven-plugin-api</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.apache.maven.surefire</groupId>
       <artifactId>maven-surefire-common</artifactId>
     </dependency>
-    <dependency>
-      <groupId>org.apache.maven.surefire</groupId>
-      <artifactId>surefire-api</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven</groupId>
-      <artifactId>maven-toolchain</artifactId>
-    </dependency>
-    <dependency>
-      <groupId>org.apache.maven.plugin-tools</groupId>
-      <artifactId>maven-plugin-annotations</artifactId>
-      <scope>compile</scope>
-    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
----------------------------------------------------------------------
diff --git a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
index a546853..7126247 100644
--- a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
@@ -39,8 +39,32 @@ Shutdown of Forked JVM
 
 * Pinging forked JVM
 
+  << Since ${thisPlugin} Plugin 2.20.1 ping is platform dependent and fallbacks to old mechanism if PID of Maven
+  process or platform is not recognized, native commands fail in Java. >>
+
+  Simply the mechanism checks the <<< Maven PID >>> is still alive and it is not reused by OS in another application.
+  If Maven process has died, the forked JVM is killed.
+
+  << Implementation: >> The <<< Maven PID >>> is determined by:
+
+   * resolving the link <<< /proc/self >>> on Linux, or
+
+   * the JMX call <<< ManagementFactory.getRuntimeMXBean().getName() >>>, or
+
+   * Java 9 call <<< ProcessHandle.current().pid() >>>.
+
+   []
+
+   On Unix like systems the process' uptime is determined by native command <<< (/usr)/bin/ps -o etime= -p [PID] >>>.
+
+   On Windows the start time is determined using <<< wmic process where (ProcessId=[PID]) get CreationDate >>>
+   in the forked JVM.
+
+
+  << Since ${thisPlugin} Plugin 2.19 the old mechanism is significantly slower: >>
+
   The master process sends NOOP command to a forked JVM every 10 seconds.
-  Forked JVM is waiting for the command every 20 seconds.
+  Forked JVM is waiting for the command every 30 seconds.
   If the master process is killed (received SIGKILL signal) or shutdown 
   (pressed CTRL+C, received SIGTERM signal), forked JVM is killed after
   timing out waiting period.

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/maven-surefire-report-plugin/pom.xml
----------------------------------------------------------------------
diff --git a/maven-surefire-report-plugin/pom.xml b/maven-surefire-report-plugin/pom.xml
index a4fe7e2..93a2e80 100644
--- a/maven-surefire-report-plugin/pom.xml
+++ b/maven-surefire-report-plugin/pom.xml
@@ -48,10 +48,6 @@
 
   <dependencies>
     <dependency>
-      <groupId>org.apache.maven.surefire</groupId>
-      <artifactId>surefire-logger-api</artifactId>
-    </dependency>
-    <dependency>
       <groupId>org.apache.maven</groupId>
       <artifactId>maven-project</artifactId>
     </dependency>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 962a5bf..6ba5602 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,8 +91,13 @@
     <mavenVersion>2.2.1</mavenVersion>
     <!-- <shadedVersion>2.12.4</shadedVersion> commented out due to https://issues.apache.org/jira/browse/MRELEASE-799 -->
     <mavenPluginPluginVersion>3.3</mavenPluginPluginVersion>
+    <commonsLang3Version>3.5</commonsLang3Version>
+    <commonsIoVersion>2.5</commonsIoVersion>
+    <mavenSharedUtilsVersion>0.9</mavenSharedUtilsVersion>
     <maven.surefire.scm.devConnection>scm:git:https://git-wip-us.apache.org/repos/asf/maven-surefire.git</maven.surefire.scm.devConnection>
     <maven.site.path>surefire-archives/surefire-LATEST</maven.site.path>
+    <!-- Override with Jigsaw JRE 9 -->
+    <test.jre>${java.home}/..</test.jre>
   </properties>
 
   <dependencyManagement>
@@ -105,12 +110,12 @@
       <dependency>
         <groupId>org.apache.commons</groupId>
         <artifactId>commons-lang3</artifactId>
-        <version>3.1</version>
+        <version>${commonsLang3Version}</version>
       </dependency>
       <dependency>
         <groupId>commons-io</groupId>
         <artifactId>commons-io</artifactId>
-        <version>2.2</version>
+        <version>${commonsIoVersion}</version>
       </dependency>
       <dependency>
         <groupId>org.apache.maven.surefire</groupId>
@@ -215,7 +220,13 @@
       <dependency>
         <groupId>org.apache.maven.shared</groupId>
         <artifactId>maven-shared-utils</artifactId>
-        <version>0.9</version>
+        <version>${mavenSharedUtilsVersion}</version>
+        <exclusions>
+          <exclusion>
+            <groupId>com.google.code.findbugs</groupId>
+            <artifactId>jsr305</artifactId>
+          </exclusion>
+        </exclusions>
       </dependency>
       <dependency>
         <groupId>org.apache.maven.shared</groupId>
@@ -377,7 +388,7 @@
         </plugin>
         <plugin>
           <artifactId>maven-shade-plugin</artifactId>
-          <version>1.5</version>
+          <version>3.0.0</version>
         </plugin>
         <plugin>
           <artifactId>maven-plugin-plugin</artifactId>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-api/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-api/pom.xml b/surefire-api/pom.xml
index 7e407d2..ec5c664 100644
--- a/surefire-api/pom.xml
+++ b/surefire-api/pom.xml
@@ -40,6 +40,11 @@
       <groupId>org.apache.maven.shared</groupId>
       <artifactId>maven-shared-utils</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.google.code.findbugs</groupId>
+      <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
+    </dependency>
   </dependencies>
 
   <build>
@@ -47,6 +52,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
+          <jvm>${test.jre}/bin/java</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
@@ -74,7 +80,6 @@
               <artifactSet>
                 <includes>
                   <include>org.apache.maven.shared:maven-shared-utils</include>
-                  <include>commons-lang:commons-lang</include>
                 </includes>
               </artifactSet>
               <relocations>
@@ -82,10 +87,6 @@
                   <pattern>org.apache.maven.shared</pattern>
                   <shadedPattern>org.apache.maven.surefire.shade.org.apache.maven.shared</shadedPattern>
                 </relocation>
-                <relocation>
-                  <pattern>org.apache.commons.lang</pattern>
-                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern>
-                </relocation>
               </relocations>
             </configuration>
           </execution>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
index ed7d4fa..c3d80ea 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java
@@ -438,7 +438,7 @@ public final class CommandReader
                     DumpErrorSingleton.getSingleton().dumpStreamException( e, msg );
 
                     exitByConfiguration();
-                    // does not go to finally
+                    // does not go to finally for non-default config: Shutdown.EXIT or Shutdown.KILL
                 }
             }
             catch ( IOException e )
@@ -493,7 +493,7 @@ public final class CommandReader
                 {
                     Runtime.getRuntime().halt( 1 );
                 }
-                // else is default: should not happen; otherwise you missed enum case
+                // else is default: other than Shutdown.DEFAULT should not happen; otherwise you missed enum case
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
index 6844dda..1be4e06 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java
@@ -28,10 +28,6 @@ import java.lang.reflect.Method;
  */
 public final class ReflectionUtils
 {
-    private static final Class[] NO_ARGS = new Class[0];
-
-    private static final Object[] NO_ARGS_VALUES = new Object[0];
-
     private ReflectionUtils()
     {
         throw new IllegalStateException( "no instantiable constructor" );
@@ -68,8 +64,8 @@ public final class ReflectionUtils
 
     public static Object invokeGetter( Object instance, String methodName )
     {
-        final Method method = getMethod( instance, methodName, NO_ARGS );
-        return invokeMethodWithArray( instance, method, NO_ARGS_VALUES );
+        final Method method = getMethod( instance, methodName );
+        return invokeMethodWithArray( instance, method );
     }
 
     public static Constructor getConstructor( Class<?> clazz, Class<?>... arguments )
@@ -245,4 +241,57 @@ public final class ReflectionUtils
             throw new SurefireReflectionException( e );
         }
     }
+
+    /**
+     * Invoker of public static no-argument method.
+     *
+     * @param clazz         class on which public static no-argument {@code methodName} is invoked
+     * @param methodName    public static no-argument method to be called
+     * @return value returned by {@code methodName}
+     * @throws RuntimeException if no such method found
+     * @throws SurefireReflectionException if the method could not be called or threw an exception
+     */
+    public static Object invokeStaticMethod( Class<?> clazz, String methodName )
+    {
+        Method method = getMethod( clazz, methodName );
+        return invokeMethodWithArray( null, method );
+    }
+
+    /**
+     * Method chain invoker.
+     *
+     * @param classesChain        classes to invoke on method chain
+     * @param noArgMethodNames    chain of public methods to call
+     * @param fallback            returned value if a chain could not be invoked due to an error
+     * @return successfully returned value from the last method call; {@code fallback} otherwise
+     * @throws IllegalArgumentException if {@code classes} and {@code noArgMethodNames} have different array length
+     */
+    public static Object invokeMethodChain( Class<?>[] classesChain, String[] noArgMethodNames, Object fallback )
+    {
+        if ( classesChain.length != noArgMethodNames.length )
+        {
+            throw new IllegalArgumentException( "arrays must have the same length" );
+        }
+        Object obj = null;
+        try
+        {
+            for ( int i = 0, len = noArgMethodNames.length; i < len; i++ )
+            {
+                if ( i == 0 )
+                {
+                    obj = invokeStaticMethod( classesChain[i], noArgMethodNames[i] );
+                }
+                else
+                {
+                    Method method = getMethod( classesChain[i], noArgMethodNames[i] );
+                    obj = invokeMethodWithArray( obj, method );
+                }
+            }
+            return obj;
+        }
+        catch ( RuntimeException e )
+        {
+            return fallback;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java
deleted file mode 100644
index 7881b5d..0000000
--- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package org.apache.maven.surefire.util.internal;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.apache.maven.surefire.util.ReflectionUtils;
-
-import java.lang.reflect.Method;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import static java.lang.Integer.parseInt;
-import static java.lang.Math.pow;
-
-/**
- * JDK 9 support.
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.20.1
- */
-public final class SystemUtils
-{
-    private static final Pattern JAVA_SPEC_VERSION_PATTERN = Pattern.compile( "(\\d+)(\\.?)(\\d*).*" );
-    private static final double JAVA_SPEC_VERSION = javaSpecVersion();
-
-    public SystemUtils()
-    {
-        throw new IllegalStateException( "no instantiable constructor" );
-    }
-
-    public static ClassLoader platformClassLoader()
-    {
-        if ( JAVA_SPEC_VERSION < 9 )
-        {
-            return null;
-        }
-
-        return reflectClassLoader( ClassLoader.class, "getPlatformClassLoader" );
-    }
-
-    public static double javaSpecVersion()
-    {
-        return extractJavaSpecVersion( System.getProperty( "java.specification.version" ) );
-    }
-
-    static ClassLoader reflectClassLoader( Class<?> target, String getterMethodName )
-    {
-        try
-        {
-            Method getter = ReflectionUtils.getMethod( target, getterMethodName );
-            return (ClassLoader) ReflectionUtils.invokeMethodWithArray( null, getter );
-        }
-        catch ( RuntimeException e )
-        {
-            return null;
-        }
-    }
-
-    static double extractJavaSpecVersion( String property )
-    {
-        Matcher versionRegexMatcher = JAVA_SPEC_VERSION_PATTERN.matcher( property );
-        int groups = versionRegexMatcher.groupCount();
-        if ( !versionRegexMatcher.matches() )
-        {
-            throw new IllegalStateException( "Java Spec Version does not match the pattern "
-                                                     + JAVA_SPEC_VERSION_PATTERN
-            );
-        }
-
-        if ( groups >= 3 )
-        {
-            String majorVersion = versionRegexMatcher.group( 1 );
-            String minorVersion = versionRegexMatcher.group( 3 );
-            int major = parseInt( majorVersion );
-            double minor = minorVersion.isEmpty() ? 0 : parseInt( minorVersion ) / pow( 10, minorVersion.length() );
-            return major + minor;
-        }
-        else
-        {
-            return parseInt( versionRegexMatcher.group( 0 ) );
-        }
-    }
-}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
index dbf46ea..66cca64 100644
--- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
+++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java
@@ -32,6 +32,7 @@ import org.apache.maven.surefire.testset.FundamentalFilterTest;
 import org.apache.maven.surefire.testset.ResolvedTestTest;
 import org.apache.maven.surefire.testset.TestListResolverTest;
 import org.apache.maven.surefire.util.DefaultDirectoryScannerTest;
+import org.apache.maven.surefire.util.ReflectionUtilsTest;
 import org.apache.maven.surefire.util.RunOrderCalculatorTest;
 import org.apache.maven.surefire.util.RunOrderTest;
 import org.apache.maven.surefire.util.ScanResultTest;
@@ -40,7 +41,6 @@ import org.apache.maven.surefire.util.UrlUtilsTest;
 import org.apache.maven.surefire.util.internal.ConcurrencyUtilsTest;
 import org.apache.maven.surefire.util.internal.ImmutableMapTest;
 import org.apache.maven.surefire.util.internal.StringUtilsTest;
-import org.apache.maven.surefire.util.internal.SystemUtilsTest;
 import org.junit.runner.RunWith;
 import org.junit.runners.Suite;
 
@@ -69,8 +69,8 @@ import org.junit.runners.Suite;
     UrlUtilsTest.class,
     SpecificTestClassFilterTest.class,
     FundamentalFilterTest.class,
-    SystemUtilsTest.class,
-    ImmutableMapTest.class
+    ImmutableMapTest.class,
+    ReflectionUtilsTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java
new file mode 100644
index 0000000..41f9702
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java
@@ -0,0 +1,114 @@
+package org.apache.maven.surefire.util;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+/**
+ * Unit test for {@link ReflectionUtils}.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class ReflectionUtilsTest
+{
+    @Test(expected = RuntimeException.class)
+    public void shouldNotInvokeStaticMethod()
+    {
+        ReflectionUtils.invokeStaticMethod( ReflectionUtilsTest.class, "notCallable" );
+    }
+
+    @Test
+    public void shouldInvokeStaticMethod()
+    {
+        Object o = ReflectionUtils.invokeStaticMethod( ReflectionUtilsTest.class, "callable" );
+        assertThat( o )
+                .isEqualTo( 3L );
+    }
+
+    @Test
+    public void shouldInvokeMethodChain()
+    {
+        Class<?>[] classes1 = { A.class, A.class };
+        String[] chain = { "current", "pid" };
+        Object o = ReflectionUtils.invokeMethodChain( classes1, chain, null );
+        assertThat( o )
+                .isEqualTo( 3L );
+
+        Class<?>[] classes2 = { A.class, A.class, B.class };
+        String[] longChain = { "current", "createB", "pid" };
+        o = ReflectionUtils.invokeMethodChain( classes2, longChain, null );
+        assertThat( o )
+                .isEqualTo( 1L );
+    }
+
+    @Test
+    public void shouldInvokeFallbackOnMethodChain()
+    {
+        Class<?>[] classes1 = { A.class, A.class };
+        String[] chain = { "current", "abc" };
+        Object o = ReflectionUtils.invokeMethodChain( classes1, chain, 5L );
+        assertThat( o )
+                .isEqualTo( 5L );
+
+        Class<?>[] classes2 = { A.class, B.class, B.class };
+        String[] longChain = { "current", "createB", "abc" };
+        o = ReflectionUtils.invokeMethodChain( classes2, longChain, 6L );
+        assertThat( o )
+                .isEqualTo( 6L );
+    }
+
+    private static void notCallable()
+    {
+    }
+
+    public static long callable()
+    {
+        return 3L;
+    }
+
+    public static class A
+    {
+        public static A current()
+        {
+            return new A();
+        }
+
+        public long pid()
+        {
+            return 3L;
+        }
+
+        public B createB()
+        {
+            return new B();
+        }
+    }
+
+    public static class B
+    {
+        public long pid()
+        {
+            return 1L;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java
deleted file mode 100644
index bbd9b3c..0000000
--- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package org.apache.maven.surefire.util.internal;
-
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import org.junit.Test;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-/**
- * Test of {@link SystemUtils}.
- *
- * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
- * @since 2.20.1
- */
-public class SystemUtilsTest
-{
-    @Test
-    public void shouldBeJava9()
-    {
-        String simpleVersion = "9";
-        double version = SystemUtils.extractJavaSpecVersion( simpleVersion );
-        assertThat( version ).isEqualTo( 9d );
-    }
-
-    @Test
-    public void shouldBeJava8()
-    {
-        String simpleVersion = "1.8";
-        double version = SystemUtils.extractJavaSpecVersion( simpleVersion );
-        assertThat( version ).isEqualTo( 1.8d );
-    }
-
-    @Test
-    public void shouldBeJavaMajorAndMinor()
-    {
-        String simpleVersion = "12.345.6";
-        double version = SystemUtils.extractJavaSpecVersion( simpleVersion );
-        assertThat( version ).isEqualTo( 12.345d );
-    }
-
-    @Test
-    public void shouldBeCurrentJavaVersion()
-    {
-        Double parsedVersion = SystemUtils.javaSpecVersion();
-        String expectedVersion = System.getProperty( "java.specification.version" );
-        assertThat( parsedVersion.toString() ).isEqualTo( expectedVersion );
-    }
-
-    @Test
-    public void shouldBePlatformClassLoader()
-    {
-        ClassLoader cl = SystemUtils.platformClassLoader();
-        if ( SystemUtils.javaSpecVersion() < 9 )
-        {
-            assertThat( cl ).isNull();
-        }
-        else
-        {
-            assertThat( cl ).isNotNull();
-        }
-    }
-
-    @Test
-    public void shouldNotFindClassLoader()
-    {
-        ClassLoader cl = SystemUtils.reflectClassLoader( getClass(), "_getPlatformClassLoader_" );
-        assertThat( cl ).isNull();
-    }
-
-    @Test
-    public void shouldFindClassLoader()
-    {
-        ClassLoader cl = SystemUtils.reflectClassLoader( getClass(), "getPlatformClassLoader" );
-        assertThat( cl ).isSameAs( ClassLoader.getSystemClassLoader() );
-    }
-
-    public static ClassLoader getPlatformClassLoader()
-    {
-        return ClassLoader.getSystemClassLoader();
-    }
-}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-booter/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index b79cceb..8fdaf7e 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -35,12 +35,47 @@
     <dependency>
       <groupId>org.apache.maven.surefire</groupId>
       <artifactId>surefire-api</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>org.apache.maven.shared</groupId>
+          <artifactId>maven-shared-utils</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.maven.shared</groupId>
+      <artifactId>maven-shared-utils</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
     </dependency>
   </dependencies>
 
   <build>
     <plugins>
       <plugin>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>build-test-classpath</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>build-classpath</goal>
+            </goals>
+            <configuration>
+              <includeScope>test</includeScope>
+              <outputFile>target/test-classpath/cp.txt</outputFile>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <dependencies>
           <dependency>
@@ -50,6 +85,7 @@
           </dependency>
         </dependencies>
         <configuration>
+          <jvm>${test.jre}/bin/java</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
@@ -69,13 +105,18 @@
               <minimizeJar>true</minimizeJar>
               <artifactSet>
                 <includes>
-                  <include>commons-lang:commons-lang</include>
+                  <include>org.apache.commons:commons-lang3</include>
+                  <include>commons-io:commons-io</include>
                 </includes>
               </artifactSet>
               <relocations>
                 <relocation>
-                  <pattern>org.apache.commons.lang</pattern>
-                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern>
+                  <pattern>org.apache.commons.lang3</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang3</shadedPattern>
+                </relocation>
+                <relocation>
+                  <pattern>org.apache.commons.io</pattern>
+                  <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern>
                 </relocation>
               </relocations>
             </configuration>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
index c21edf8..3551910 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java
@@ -56,4 +56,5 @@ public final class BooterConstants
     public static final String FAIL_FAST_COUNT = "failFastCount";
     public static final String SHUTDOWN = "shutdown";
     public static final String SYSTEM_EXIT_TIMEOUT = "systemExitTimeout";
+    public static final String PLUGIN_PID = "pluginPid";
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
index 8fa760b..75aad1f 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java
@@ -58,6 +58,14 @@ public class BooterDeserializer
         properties = SystemPropertyManager.loadProperties( inputStream );
     }
 
+    /**
+     * @return PID of Maven process where plugin is executed; or null if PID could not be determined.
+     */
+    public Long getPluginPid()
+    {
+        return properties.getLongProperty( PLUGIN_PID );
+    }
+
     public ProviderConfiguration deserialize()
     {
         final File reportsDirectory = new File( properties.getProperty( REPORTSDIRECTORY ) );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
index 531e7a1..609be0f 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java
@@ -19,8 +19,6 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import org.apache.maven.surefire.util.internal.SystemUtils;
-
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/5cbdc7a4/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index 1e3863e..d3c6306 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -22,7 +22,6 @@ package org.apache.maven.surefire.booter;
 import org.apache.maven.surefire.providerapi.ProviderParameters;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.LegacyPojoStackTraceWriter;
-import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
 import org.apache.maven.surefire.testset.TestSetFailedException;
@@ -30,6 +29,7 @@ import org.apache.maven.surefire.testset.TestSetFailedException;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.PrintStream;
 import java.lang.management.ManagementFactory;
@@ -37,7 +37,6 @@ import java.lang.reflect.InvocationTargetException;
 import java.security.AccessControlException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
-import java.util.concurrent.ExecutorService;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledThreadPoolExecutor;
 import java.util.concurrent.Semaphore;
@@ -45,14 +44,9 @@ import java.util.concurrent.ThreadFactory;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.lang.Math.max;
-import static java.lang.System.err;
-import static java.lang.System.out;
-import static java.lang.System.setErr;
-import static java.lang.System.setOut;
 import static java.lang.Thread.currentThread;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.apache.maven.surefire.booter.CommandReader.getReader;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE;
 import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR;
 import static org.apache.maven.surefire.booter.ForkingRunListener.encode;
@@ -73,107 +67,91 @@ import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringFo
  */
 public final class ForkedBooter
 {
-    private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30;
-    private static final long PING_TIMEOUT_IN_SECONDS = 20;
-    private static final long ONE_SECOND_IN_MILLIS = 1000;
-    private static final CommandReader COMMAND_READER = startupMasterProcessReader();
+    private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30L;
+    private static final long PING_TIMEOUT_IN_SECONDS = 30L;
+    private static final long ONE_SECOND_IN_MILLIS = 1000L;
 
-    private static volatile ScheduledThreadPoolExecutor jvmTerminator;
-    private static volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS;
+    private final CommandReader commandReader = CommandReader.getReader();
+    private final PrintStream originalOut = System.out;
 
-    /**
-     * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
-     * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown.
-     *
-     * @param args Commandline arguments
-     */
-    public static void main( String... args )
-    {
-        final ExecutorService pingScheduler = isDebugging() ? null : listenToShutdownCommands();
-        final PrintStream originalOut = out;
-        try
-        {
-            final String tmpDir = args[0];
-            final String dumpFileName = args[1];
-            final String surefirePropsFileName = args[2];
+    private volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS;
+    private volatile PingScheduler pingScheduler;
 
-            BooterDeserializer booterDeserializer =
-                    new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) );
-            if ( args.length > 3 )
-            {
-                final String effectiveSystemPropertiesFileName = args[3];
-                setSystemProperties( new File( tmpDir, effectiveSystemPropertiesFileName ) );
-            }
+    private ScheduledThreadPoolExecutor jvmTerminator;
+    private ProviderConfiguration providerConfiguration;
+    private StartupConfiguration startupConfiguration;
+    private Object testSet;
 
-            final ProviderConfiguration providerConfiguration = booterDeserializer.deserialize();
-            DumpErrorSingleton.getSingleton().init( dumpFileName, providerConfiguration.getReporterConfiguration() );
+    private void setupBooter( String tmpDir, String dumpFileName, String surefirePropsFileName,
+                              String effectiveSystemPropertiesFileName )
+            throws IOException, SurefireExecutionException
+    {
+        BooterDeserializer booterDeserializer =
+                new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) );
+        // todo: print PID in debug console logger in version 2.20.2
+        pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid() );
+        setSystemProperties( new File( tmpDir, effectiveSystemPropertiesFileName ) );
 
-            final StartupConfiguration startupConfiguration = booterDeserializer.getProviderConfiguration();
-            systemExitTimeoutInSeconds =
-                    providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
-            final TypeEncodedValue forkedTestSet = providerConfiguration.getTestForFork();
-            final boolean readTestsFromInputStream = providerConfiguration.isReadTestsFromInStream();
+        providerConfiguration = booterDeserializer.deserialize();
+        DumpErrorSingleton.getSingleton().init( dumpFileName, providerConfiguration.getReporterConfiguration() );
 
-            final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
-            if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() )
-            {
-                classpathConfiguration.trickClassPathWhenManifestOnlyClasspath();
-            }
+        startupConfiguration = booterDeserializer.getProviderConfiguration();
+        systemExitTimeoutInSeconds =
+                providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS );
 
-            final ClassLoader classLoader = currentThread().getContextClassLoader();
-            classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() );
-            startupConfiguration.writeSurefireTestClasspathProperty();
+        ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration();
+        if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() )
+        {
+            classpathConfiguration.trickClassPathWhenManifestOnlyClasspath();
+        }
 
-            final Object testSet;
-            if ( forkedTestSet != null )
-            {
-                testSet = forkedTestSet.getDecodedValue( classLoader );
-            }
-            else if ( readTestsFromInputStream )
-            {
-                testSet = new LazyTestsToRun( originalOut );
-            }
-            else
-            {
-                testSet = null;
-            }
+        ClassLoader classLoader = currentThread().getContextClassLoader();
+        classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() );
+        startupConfiguration.writeSurefireTestClasspathProperty();
+        testSet = createTestSet( providerConfiguration.getTestForFork(),
+                                       providerConfiguration.isReadTestsFromInStream(), classLoader );
+    }
 
-            try
-            {
-                runSuitesInProcess( testSet, startupConfiguration, providerConfiguration, originalOut );
-            }
-            catch ( InvocationTargetException t )
-            {
-                DumpErrorSingleton.getSingleton().dumpException( t );
-                StackTraceWriter stackTraceWriter =
+    private void execute()
+    {
+        try
+        {
+            runSuitesInProcess();
+        }
+        catch ( InvocationTargetException t )
+        {
+            DumpErrorSingleton.getSingleton().dumpException( t );
+            StackTraceWriter stackTraceWriter =
                     new LegacyPojoStackTraceWriter( "test subsystem", "no method", t.getTargetException() );
-                StringBuilder stringBuilder = new StringBuilder();
-                encode( stringBuilder, stackTraceWriter, false );
-                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" , originalOut );
-            }
-            catch ( Throwable t )
-            {
-                DumpErrorSingleton.getSingleton().dumpException( t );
-                StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
-                StringBuilder stringBuilder = new StringBuilder();
-                encode( stringBuilder, stackTraceWriter, false );
-                encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n", originalOut );
-            }
-            acknowledgedExit( originalOut, pingScheduler );
+            StringBuilder stringBuilder = new StringBuilder();
+            encode( stringBuilder, stackTraceWriter, false );
+            encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" );
         }
         catch ( Throwable t )
         {
             DumpErrorSingleton.getSingleton().dumpException( t );
-            // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace
-            // noinspection UseOfSystemOutOrSystemErr
-            t.printStackTrace( err );
-            cancelPingScheduler( pingScheduler );
-            // noinspection ProhibitedExceptionThrown,CallToSystemExit
-            exit( 1 );
+            StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t );
+            StringBuilder stringBuilder = new StringBuilder();
+            encode( stringBuilder, stackTraceWriter, false );
+            encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" );
         }
+        acknowledgedExit();
     }
 
-    private static void cancelPingScheduler( final ExecutorService pingScheduler )
+    private Object createTestSet( TypeEncodedValue forkedTestSet, boolean readTestsFromCommandReader, ClassLoader cl )
+    {
+        if ( forkedTestSet != null )
+        {
+            return forkedTestSet.getDecodedValue( cl );
+        }
+        else if ( readTestsFromCommandReader )
+        {
+            return new LazyTestsToRun( originalOut );
+        }
+        return null;
+    }
+
+    private void cancelPingScheduler()
     {
         if ( pingScheduler != null )
         {
@@ -197,23 +175,50 @@ public final class ForkedBooter
         }
     }
 
-    private static CommandReader startupMasterProcessReader()
+    private PingScheduler listenToShutdownCommands( Long pluginPid )
     {
-        return getReader();
+        commandReader.addShutdownListener( createExitHandler() );
+        AtomicBoolean pingDone = new AtomicBoolean( true );
+        commandReader.addNoopListener( createPingHandler( pingDone ) );
+
+        PingScheduler pingMechanisms = new PingScheduler( createPingScheduler(),
+                                                          pluginPid == null ? null : new PpidChecker( pluginPid ) );
+        if ( pingMechanisms.pluginProcessChecker != null )
+        {
+            Runnable checkerJob = processCheckerJob( pingMechanisms );
+            pingMechanisms.pingScheduler.scheduleWithFixedDelay( checkerJob, 0L, 1L, SECONDS );
+        }
+        Runnable pingJob = createPingJob( pingDone, pingMechanisms.pluginProcessChecker );
+        pingMechanisms.pingScheduler.scheduleAtFixedRate( pingJob, 0L, PING_TIMEOUT_IN_SECONDS, SECONDS );
+
+        return pingMechanisms;
     }
 
-    private static ExecutorService listenToShutdownCommands()
+    private Runnable processCheckerJob( final PingScheduler pingMechanism )
     {
-        COMMAND_READER.addShutdownListener( createExitHandler() );
-        AtomicBoolean pingDone = new AtomicBoolean( true );
-        COMMAND_READER.addNoopListener( createPingHandler( pingDone ) );
-        Runnable pingJob = createPingJob( pingDone );
-        ScheduledExecutorService pingScheduler = createPingScheduler();
-        pingScheduler.scheduleAtFixedRate( pingJob, 0, PING_TIMEOUT_IN_SECONDS, SECONDS );
-        return pingScheduler;
+        return new Runnable()
+        {
+            @Override
+            public void run()
+            {
+                try
+                {
+                    if ( pingMechanism.pluginProcessChecker.canUse()
+                                 && !pingMechanism.pluginProcessChecker.isProcessAlive()
+                                 && !pingMechanism.pingScheduler.isShutdown() )
+                    {
+                        kill();
+                    }
+                }
+                catch ( Exception e )
+                {
+                    // nothing to do
+                }
+            }
+        };
     }
 
-    private static CommandListener createPingHandler( final AtomicBoolean pingDone )
+    private CommandListener createPingHandler( final AtomicBoolean pingDone )
     {
         return new CommandListener()
         {
@@ -225,7 +230,7 @@ public final class ForkedBooter
         };
     }
 
-    private static CommandListener createExitHandler()
+    private CommandListener createExitHandler()
     {
         return new CommandListener()
         {
@@ -239,6 +244,7 @@ public final class ForkedBooter
                 }
                 else if ( shutdown.isExit() )
                 {
+                    cancelPingScheduler();
                     exit( 1 );
                 }
                 // else refers to shutdown=testset, but not used now, keeping reader open
@@ -246,98 +252,88 @@ public final class ForkedBooter
         };
     }
 
-    private static Runnable createPingJob( final AtomicBoolean pingDone  )
+    private Runnable createPingJob( final AtomicBoolean pingDone, final PpidChecker pluginProcessChecker  )
     {
         return new Runnable()
         {
             @Override
             public void run()
             {
-                boolean hasPing = pingDone.getAndSet( false );
-                if ( !hasPing )
+                if ( !canUseNewPingMechanism( pluginProcessChecker ) )
                 {
-                    kill();
+                    boolean hasPing = pingDone.getAndSet( false );
+                    if ( !hasPing )
+                    {
+                        kill();
+                    }
                 }
             }
         };
     }
 
-    private static void encodeAndWriteToOutput( String string, PrintStream out )
+    private void encodeAndWriteToOutput( String string )
     {
         byte[] encodeBytes = encodeStringForForkCommunication( string );
         //noinspection SynchronizationOnLocalVariableOrMethodParameter
-        synchronized ( out )
+        synchronized ( originalOut )
         {
-            out.write( encodeBytes, 0, encodeBytes.length );
-            out.flush();
+            originalOut.write( encodeBytes, 0, encodeBytes.length );
+            originalOut.flush();
         }
     }
 
-    private static void kill()
+    private void kill()
+    {
+        kill( 1 );
+    }
+
+    private void kill( int returnCode )
     {
-        COMMAND_READER.stop();
-        Runtime.getRuntime().halt( 1 );
+        commandReader.stop();
+        Runtime.getRuntime().halt( returnCode );
     }
 
-    private static void exit( int returnCode )
+    private void exit( int returnCode )
     {
         launchLastDitchDaemonShutdownThread( returnCode );
-        COMMAND_READER.stop();
         System.exit( returnCode );
     }
 
-    private static void acknowledgedExit( PrintStream originalOut, ExecutorService pingScheduler )
+    private void acknowledgedExit()
     {
         final Semaphore barrier = new Semaphore( 0 );
-        COMMAND_READER.addByeAckListener( new CommandListener()
-                                  {
-                                      @Override
-                                      public void update( Command command )
-                                      {
-                                          barrier.release();
-                                      }
-                                  }
+        commandReader.addByeAckListener( new CommandListener()
+                                          {
+                                              @Override
+                                              public void update( Command command )
+                                              {
+                                                  barrier.release();
+                                              }
+                                          }
         );
-        encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut );
+        encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n" );
         launchLastDitchDaemonShutdownThread( 0 );
         long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS );
         acquireOnePermit( barrier, timeoutMillis );
-        cancelPingScheduler( pingScheduler );
-        COMMAND_READER.stop();
+        cancelPingScheduler();
+        commandReader.stop();
         System.exit( 0 );
     }
 
-    private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis )
-    {
-        try
-        {
-            return barrier.tryAcquire( timeoutMillis, MILLISECONDS );
-        }
-        catch ( InterruptedException e )
-        {
-            return true;
-        }
-    }
-
-    private static RunResult runSuitesInProcess( Object testSet, StartupConfiguration startupConfiguration,
-                                                 ProviderConfiguration providerConfiguration,
-                                                 PrintStream originalSystemOut )
+    private RunResult runSuitesInProcess()
         throws SurefireExecutionException, TestSetFailedException, InvocationTargetException
     {
-        final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, originalSystemOut );
-
-        return invokeProviderInSameClassLoader( testSet, factory, providerConfiguration, true, startupConfiguration,
-                                                      false );
+        ForkingReporterFactory factory = createForkingReporterFactory();
+        return invokeProviderInSameClassLoader( factory );
     }
 
-    private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration,
-                                                                 PrintStream originalSystemOut )
+    private ForkingReporterFactory createForkingReporterFactory()
     {
         final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace();
-        return new ForkingReporterFactory( trimStackTrace, originalSystemOut );
+        return new ForkingReporterFactory( trimStackTrace, originalOut );
     }
 
-    private static synchronized ScheduledThreadPoolExecutor getJvmTerminator()
+    private synchronized ScheduledThreadPoolExecutor getJvmTerminator()
     {
         if ( jvmTerminator == null )
         {
@@ -345,71 +341,34 @@ public final class ForkedBooter
                     newDaemonThreadFactory( "last-ditch-daemon-shutdown-thread-" + systemExitTimeoutInSeconds + "s" );
             jvmTerminator = new ScheduledThreadPoolExecutor( 1, threadFactory );
             jvmTerminator.setMaximumPoolSize( 1 );
-            return jvmTerminator;
-        }
-        else
-        {
-            return jvmTerminator;
         }
-    }
-
-    private static ScheduledExecutorService createPingScheduler()
-    {
-        ThreadFactory threadFactory = newDaemonThreadFactory( "ping-" + PING_TIMEOUT_IN_SECONDS + "s" );
-        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory );
-        executor.setMaximumPoolSize( 1 );
-        executor.prestartCoreThread();
-        return executor;
+        return jvmTerminator;
     }
 
     @SuppressWarnings( "checkstyle:emptyblock" )
-    private static void launchLastDitchDaemonShutdownThread( final int returnCode )
+    private void launchLastDitchDaemonShutdownThread( final int returnCode )
     {
         getJvmTerminator().schedule( new Runnable()
                                         {
                                             @Override
                                             public void run()
                                             {
-                                                COMMAND_READER.stop();
-                                                Runtime.getRuntime().halt( returnCode );
+                                                kill( returnCode );
                                             }
                                         }, systemExitTimeoutInSeconds, SECONDS
         );
     }
 
-    private static RunResult invokeProviderInSameClassLoader( Object testSet, Object factory,
-                                                              ProviderConfiguration providerConfig,
-                                                              boolean insideFork,
-                                                              StartupConfiguration startupConfig,
-                                                              boolean restoreStreams )
+    private RunResult invokeProviderInSameClassLoader( ForkingReporterFactory factory )
         throws TestSetFailedException, InvocationTargetException
     {
-        final PrintStream orgSystemOut = out;
-        final PrintStream orgSystemErr = err;
-        // Note that System.out/System.err are also read in the "ReporterConfiguration" instantiation
-        // in createProvider below. These are the same values as here.
-
-        try
-        {
-            return createProviderInCurrentClassloader( startupConfig, insideFork, providerConfig, factory )
-                           .invoke( testSet );
-        }
-        finally
-        {
-            if ( restoreStreams && System.getSecurityManager() == null )
-            {
-                setOut( orgSystemOut );
-                setErr( orgSystemErr );
-            }
-        }
+        return createProviderInCurrentClassloader( factory )
+                       .invoke( testSet );
     }
 
-    private static SurefireProvider createProviderInCurrentClassloader( StartupConfiguration startupConfiguration,
-                                                                        boolean isInsideFork,
-                                                                       ProviderConfiguration providerConfiguration,
-                                                                       Object reporterManagerFactory )
+    private SurefireProvider createProviderInCurrentClassloader( ForkingReporterFactory reporterManagerFactory )
     {
-        BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory, isInsideFork );
+        BaseProviderFactory bpf = new BaseProviderFactory( reporterManagerFactory, true );
         bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
         bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
         ClassLoader classLoader = currentThread().getContextClassLoader();
@@ -426,6 +385,55 @@ public final class ForkedBooter
         return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf );
     }
 
+    /**
+     * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and
+     * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown.
+     *
+     * @param args Commandline arguments
+     */
+    public static void main( String... args )
+    {
+        ForkedBooter booter = new ForkedBooter();
+        try
+        {
+            booter.setupBooter( args[0], args[1], args[2], args.length > 3 ? args[3] : null );
+            booter.execute();
+        }
+        catch ( Throwable t )
+        {
+            DumpErrorSingleton.getSingleton().dumpException( t );
+            t.printStackTrace();
+            booter.cancelPingScheduler();
+            booter.exit( 1 );
+        }
+    }
+
+    private static boolean canUseNewPingMechanism( PpidChecker pluginProcessChecker )
+    {
+        return pluginProcessChecker != null && pluginProcessChecker.canUse();
+    }
+
+    private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis )
+    {
+        try
+        {
+            return barrier.tryAcquire( timeoutMillis, MILLISECONDS );
+        }
+        catch ( InterruptedException e )
+        {
+            return true;
+        }
+    }
+
+    private static ScheduledExecutorService createPingScheduler()
+    {
+        ThreadFactory threadFactory = newDaemonThreadFactory( "ping-" + PING_TIMEOUT_IN_SECONDS + "s" );
+        ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory );
+        executor.setKeepAliveTime( 3L, SECONDS );
+        executor.setMaximumPoolSize( 2 );
+        return executor;
+    }
+
     private static InputStream createSurefirePropertiesIfFileExists( String tmpDir, String propFileName )
             throws FileNotFoundException
     {
@@ -444,4 +452,30 @@ public final class ForkedBooter
         }
         return false;
     }
+
+    private static class PingScheduler
+    {
+        private final ScheduledExecutorService pingScheduler;
+        private final PpidChecker pluginProcessChecker;
+
+        PingScheduler( ScheduledExecutorService pingScheduler, PpidChecker pluginProcessChecker )
+        {
+            this.pingScheduler = pingScheduler;
+            this.pluginProcessChecker = pluginProcessChecker;
+        }
+
+        void shutdown()
+        {
+            pingScheduler.shutdown();
+            if ( pluginProcessChecker != null )
+            {
+                pluginProcessChecker.destroyActiveCommands();
+            }
+        }
+
+        boolean isShutdown()
+        {
+            return pingScheduler.isShutdown();
+        }
+    }
 }


Mime
View raw message