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_4
Date Fri, 21 Jul 2017 12:14:08 GMT
SUREFIRE-1302_4


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

Branch: refs/heads/SUREFIRE-1302_4
Commit: 0103691a4e57725d1a62fad825a78189576d4740
Parents: 4de017b
Author: Tibor17 <tibordigana@apache.org>
Authored: Wed Jul 19 01:21:41 2017 +0200
Committer: Tibor17 <tibordigana@apache.org>
Committed: Fri Jul 21 14:13:18 2017 +0200

----------------------------------------------------------------------
 maven-failsafe-plugin/pom.xml                   |  17 -
 maven-surefire-common/pom.xml                   |  13 +-
 .../plugin/surefire/SurefireProperties.java     |   8 +
 .../surefire/booterclient/BooterSerializer.java |   6 +-
 .../surefire/booterclient/ForkStarter.java      |   6 +-
 .../surefire/report/FileReporterUtils.java      |  11 +-
 ...erDeserializerProviderConfigurationTest.java |   3 +-
 ...terDeserializerStartupConfigurationTest.java |   3 +-
 maven-surefire-plugin/pom.xml                   |  17 -
 .../src/site/apt/examples/shutdown.apt.vm       |  26 +-
 maven-surefire-report-plugin/pom.xml            |   4 -
 pom.xml                                         |  17 +-
 surefire-api/pom.xml                            |  10 +-
 .../maven/surefire/booter/CommandReader.java    |   4 +-
 .../maven/surefire/util/ReflectionUtils.java    |  49 ++-
 .../surefire/util/internal/SystemUtils.java     |  99 -----
 .../java/org/apache/maven/JUnit4SuiteTest.java  |   4 +-
 .../surefire/util/ReflectionUtilsTest.java      | 110 +++++
 .../surefire/util/internal/SystemUtilsTest.java |  98 -----
 surefire-booter/pom.xml                         |  25 +-
 .../maven/surefire/booter/BooterConstants.java  |   1 +
 .../surefire/booter/BooterDeserializer.java     |   8 +
 .../apache/maven/surefire/booter/Classpath.java |   2 -
 .../maven/surefire/booter/ForkedBooter.java     | 419 ++++++++++---------
 .../maven/surefire/booter/PpidChecker.java      | 291 +++++++++++++
 .../maven/surefire/booter/ProcessInfo.java      | 103 +++++
 .../surefire/booter/PropertiesWrapper.java      |   6 +
 .../maven/surefire/booter/SystemUtils.java      | 200 +++++++++
 .../maven/surefire/booter/JUnit4SuiteTest.java  |   4 +-
 .../maven/surefire/booter/PpidCheckerTest.java  | 131 ++++++
 .../maven/surefire/booter/SystemUtilsTest.java  | 143 +++++++
 ...urefire1295AttributeJvmCrashesToTestsIT.java |   6 +-
 32 files changed, 1373 insertions(+), 471 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0103691a/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/0103691a/maven-surefire-common/pom.xml
----------------------------------------------------------------------
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index 4064bc2..e81a8fd 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -36,6 +36,11 @@
     <maven>2.2.1</maven>
   </prerequisites>
 
+  <properties>
+    <!-- Override with Jigsaw JDK 9 -->
+    <test.jdk>${java.home}/../bin/java</test.jdk>
+  </properties>
+
   <dependencies>
     <dependency>
       <groupId>org.apache.maven</groupId>
@@ -44,7 +49,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 +97,7 @@
     <dependency>
       <groupId>com.google.code.findbugs</groupId>
       <artifactId>jsr305</artifactId>
+      <scope>provided</scope>
     </dependency>
     <dependency>
       <groupId>org.apache.maven.shared</groupId>
@@ -160,6 +165,7 @@
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
+          <jvm>${test.jdk}</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
@@ -192,6 +198,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 +210,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/0103691a/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/0103691a/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/0103691a/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..2d2c53a 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
@@ -45,6 +45,7 @@ import org.apache.maven.surefire.booter.Shutdown;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
 import org.apache.maven.surefire.booter.SurefireExecutionException;
+import org.apache.maven.surefire.booter.SystemUtils;
 import org.apache.maven.surefire.providerapi.SurefireProvider;
 import org.apache.maven.surefire.report.StackTraceWriter;
 import org.apache.maven.surefire.suite.RunResult;
@@ -114,6 +115,8 @@ import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1;
  */
 public class ForkStarter
 {
+    private static final Long PID = SystemUtils.pid();
+
     private static final String EXECUTION_EXCEPTION = "ExecutionException";
 
     private static final long PING_IN_SECONDS = 10;
@@ -550,7 +553,8 @@ public class ForkStarter
             BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration );
 
             surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration,
-                                                             startupConfiguration, testSet, readTestsFromInStream );
+                                                                   startupConfiguration, testSet,
+                                                                   readTestsFromInStream, PID );
 
             if ( effectiveSystemProperties != null )
             {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0103691a/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/0103691a/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/0103691a/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/0103691a/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/0103691a/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/0103691a/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/0103691a/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index 962a5bf..397d5d9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,6 +91,9 @@
     <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>
   </properties>
@@ -105,12 +108,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 +218,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 +386,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/0103691a/surefire-api/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-api/pom.xml b/surefire-api/pom.xml
index 7e407d2..96c5be3 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>
@@ -74,7 +79,6 @@
               <artifactSet>
                 <includes>
                   <include>org.apache.maven.shared:maven-shared-utils</include>
-                  <include>commons-lang:commons-lang</include>
                 </includes>
               </artifactSet>
               <relocations>
@@ -82,10 +86,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/0103691a/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/0103691a/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..a0dcd66 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,45 @@ 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 firstStaticClass    class to start 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
+     */
+    public static Object invokeMethodChain( Class<?> firstStaticClass, String[] noArgMethodNames, Object fallback )
+    {
+        Object obj = null;
+        try
+        {
+            for ( int i = 0, len = noArgMethodNames.length; i < len; i++ )
+            {
+                String methodName = noArgMethodNames[i];
+                obj = i == 0 ? invokeStaticMethod( firstStaticClass, methodName ) : invokeGetter( obj, methodName );
+            }
+            return obj;
+        }
+        catch ( RuntimeException e )
+        {
+            return fallback;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0103691a/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/0103691a/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..9558bf8 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;
@@ -70,7 +71,8 @@ import org.junit.runners.Suite;
     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/0103691a/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..89bc205
--- /dev/null
+++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java
@@ -0,0 +1,110 @@
+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()
+    {
+        String[] chain = { "current", "pid" };
+        Object o = ReflectionUtils.invokeMethodChain( A.class, chain, null );
+        assertThat( o )
+                .isEqualTo( 3L );
+
+        String[] longChain = { "current", "createB", "pid" };
+        o = ReflectionUtils.invokeMethodChain( A.class, longChain, null );
+        assertThat( o )
+                .isEqualTo( 1L );
+    }
+
+    @Test
+    public void shouldInvokeFallbackOnMethodChain()
+    {
+        String[] chain = { "current", "abc" };
+        Object o = ReflectionUtils.invokeMethodChain( A.class, chain, 5L );
+        assertThat( o )
+                .isEqualTo( 5L );
+
+        String[] longChain = { "current", "createB", "abc" };
+        o = ReflectionUtils.invokeMethodChain( A.class, 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/0103691a/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/0103691a/surefire-booter/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index b79cceb..1dc7591 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -35,6 +35,20 @@
     <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.commons</groupId>
+      <artifactId>commons-lang3</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
     </dependency>
   </dependencies>
 
@@ -69,13 +83,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/0103691a/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/0103691a/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/0103691a/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/0103691a/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..90385d7 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
@@ -30,6 +30,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;
@@ -45,14 +46,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 +69,90 @@ 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;
 
-            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 PingScheduler pingScheduler;
+    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 ) );
+        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 +176,47 @@ 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 ) );
+
+        ScheduledExecutorService pingScheduler = createPingScheduler();
+        PpidChecker checker = pluginPid == null ? null : new PpidChecker( pluginPid );
+        if ( checker != null )
+        {
+            pingScheduler.scheduleWithFixedDelay( processCheckerJob( checker ), 0L, 1L, SECONDS );
+        }
+        pingScheduler.scheduleAtFixedRate( createPingJob( pingDone, checker ), 0L, PING_TIMEOUT_IN_SECONDS, SECONDS );
+
+        return new PingScheduler( pingScheduler, checker );
     }
 
-    private static ExecutorService listenToShutdownCommands()
+    private Runnable processCheckerJob( final PpidChecker pluginProcessChecker )
     {
-        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 ( pluginProcessChecker.canUse() && !pluginProcessChecker.isProcessAlive()
+                                 && !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 +228,7 @@ public final class ForkedBooter
         };
     }
 
-    private static CommandListener createExitHandler()
+    private CommandListener createExitHandler()
     {
         return new CommandListener()
         {
@@ -246,98 +249,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()
     {
-        COMMAND_READER.stop();
-        Runtime.getRuntime().halt( 1 );
+        kill( 1 );
     }
 
-    private static void exit( int returnCode )
+    private void kill( int returnCode )
+    {
+        commandReader.stop();
+        Runtime.getRuntime().halt( 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 );
+        ReporterFactory factory = createForkingReporterFactory();
+        return invokeProviderInSameClassLoader( factory );
     }
 
-    private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration,
-                                                                 PrintStream originalSystemOut )
+    private ReporterFactory 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 +338,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( ReporterFactory 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( Object reporterManagerFactory )
     {
-        BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory, isInsideFork );
+        BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory, true );
         bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() );
         bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() );
         ClassLoader classLoader = currentThread().getContextClassLoader();
@@ -426,6 +382,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 +449,30 @@ public final class ForkedBooter
         }
         return false;
     }
+
+    private static class PingScheduler
+    {
+        private final ExecutorService pingScheduler;
+        private final PpidChecker pluginProcessChecker;
+
+        PingScheduler( ExecutorService pingScheduler, PpidChecker pluginProcessChecker )
+        {
+            this.pingScheduler = pingScheduler;
+            this.pluginProcessChecker = pluginProcessChecker;
+        }
+
+        void shutdown()
+        {
+            pingScheduler.shutdown();
+            if ( pluginProcessChecker != null )
+            {
+                pluginProcessChecker.destroyActiveCommands();
+            }
+        }
+
+        boolean isShutdown()
+        {
+            return pingScheduler.isShutdown();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0103691a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java
new file mode 100644
index 0000000..b01198e
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java
@@ -0,0 +1,291 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.util.Queue;
+import java.util.Scanner;
+import java.util.StringTokenizer;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static java.lang.Long.parseLong;
+import static java.util.concurrent.TimeUnit.DAYS;
+import static java.util.concurrent.TimeUnit.HOURS;
+import static java.util.concurrent.TimeUnit.MINUTES;
+import static java.util.regex.Pattern.compile;
+import static org.apache.commons.io.IOUtils.closeQuietly;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.apache.maven.surefire.booter.ProcessInfo.ERR_PROCESS_INFO;
+import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO;
+
+/**
+ * Recognizes PID of Plugin process and determines lifetime.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+final class PpidChecker
+{
+    private static final String WMIC_CREATION_DATE = "CreationDate";
+
+    private final Queue<Process> destroyableCommands = new ConcurrentLinkedQueue<Process>();
+
+    /**
+     * The etime is in the form of [[dd-]hh:]mm:ss on Unix like systems.
+     */
+    static final Pattern UNIX_CMD_OUT_PATTERN = compile( "^(((\\d+)-)?(\\d{2}):)?(\\d{2}):(\\d{2})$" );
+
+    private final long pluginPid;
+
+    private volatile ProcessInfo pluginProcessInfo;
+    private volatile boolean stopped;
+
+    PpidChecker( long pluginPid )
+    {
+        this.pluginPid = pluginPid;
+    }
+
+    boolean canUse()
+    {
+        return pluginProcessInfo == null ? IS_OS_WINDOWS || IS_OS_UNIX : pluginProcessInfo.isValid();
+    }
+
+    /**
+     * This method can be called only after {@link #canUse()} has returned {@code true}.
+     *
+     * @return {@code true} if parent process is alive; {@code false} otherwise
+     * @throws IllegalStateException if {@link #canUse()} returns {@code false}
+     *                               or the object has been {@link #destroyActiveCommands() destroyed}
+     */
+    @SuppressWarnings( "unchecked" )
+    boolean isProcessAlive()
+    {
+        if ( !canUse() )
+        {
+            throw new IllegalStateException( "irrelevant to call isProcessAlive()" );
+        }
+
+        if ( IS_OS_WINDOWS )
+        {
+            ProcessInfo previousPluginProcessInfo = pluginProcessInfo;
+            pluginProcessInfo = windows();
+            if ( isStopped() || !pluginProcessInfo.isValid() )
+            {
+                throw new IllegalStateException( "error to read process" );
+            }
+            // let's compare creation time, should be same unless killed or PID is reused by OS into another process
+            return previousPluginProcessInfo == null
+                           || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo );
+        }
+        else if ( IS_OS_UNIX )
+        {
+            ProcessInfo previousPluginProcessInfo = pluginProcessInfo;
+            pluginProcessInfo = unix();
+            if ( isStopped() || !pluginProcessInfo.isValid() )
+            {
+                throw new IllegalStateException( "error to read process" );
+            }
+            // let's compare elapsed time, should be greater or equal if parent process is the same and still alive
+            return previousPluginProcessInfo == null
+                           || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo )
+                           || pluginProcessInfo.isTimeAfter( previousPluginProcessInfo );
+        }
+
+        throw new IllegalStateException();
+    }
+
+    // https://www.freebsd.org/cgi/man.cgi?ps(1)
+    // etimes elapsed running time, in decimal integer seconds
+
+    // http://manpages.ubuntu.com/manpages/xenial/man1/ps.1.html
+    // etimes elapsed time since the process was started, in seconds.
+
+    // http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/test/java/lang/ProcessBuilder/Basic.java#L167
+    ProcessInfo unix()
+    {
+        ProcessInfoConsumer reader = new ProcessInfoConsumer()
+        {
+            @Override
+            ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo )
+            {
+                if ( !previousProcessInfo.isValid() )
+                {
+                    Matcher matcher = UNIX_CMD_OUT_PATTERN.matcher( line );
+                    if ( matcher.matches() )
+                    {
+                        long pidUptime = fromDays( matcher )
+                                                 + fromHours( matcher )
+                                                 + fromMinutes( matcher )
+                                                 + fromSeconds( matcher );
+                        return ProcessInfo.unixProcessInfo( pluginPid, pidUptime );
+                    }
+                }
+                return previousProcessInfo;
+            }
+        };
+
+        return reader.execute( "/bin/sh", "-c", unixPathToPS() + " -o etime= -p " + pluginPid );
+    }
+
+    ProcessInfo windows()
+    {
+        ProcessInfoConsumer reader = new ProcessInfoConsumer()
+        {
+            private boolean hasHeader;
+
+            @Override
+            ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo )
+            {
+                if ( !previousProcessInfo.isValid() )
+                {
+                    StringTokenizer args = new StringTokenizer( line );
+                    if ( args.countTokens() == 1 )
+                    {
+                        if ( hasHeader )
+                        {
+                            String startTimestamp = args.nextToken();
+                            return ProcessInfo.windowsProcessInfo( pluginPid, startTimestamp );
+                        }
+                        else
+                        {
+                            hasHeader = WMIC_CREATION_DATE.equals( args.nextToken() );
+                        }
+                    }
+                }
+                return previousProcessInfo;
+            }
+        };
+        String pid = String.valueOf( pluginPid );
+        return reader.execute( "CMD", "/A", "/X", "/C",
+                                     "wmic process where (ProcessId=" + pid + ") get " + WMIC_CREATION_DATE
+        );
+    }
+
+    void destroyActiveCommands()
+    {
+        stopped = true;
+        for ( Process p = destroyableCommands.poll(); p != null; p = destroyableCommands.poll() )
+        {
+            p.destroy();
+        }
+    }
+
+    private boolean isStopped()
+    {
+        return stopped;
+    }
+
+    static String unixPathToPS()
+    {
+        return new File( "/usr/bin/ps" ).canExecute() ? "/usr/bin/ps" : "/bin/ps";
+    }
+
+    static long fromDays( Matcher matcher )
+    {
+        String s = matcher.group( 3 );
+        return s == null ? 0L : DAYS.toSeconds( parseLong( s ) );
+    }
+
+    static long fromHours( Matcher matcher )
+    {
+        String s = matcher.group( 4 );
+        return s == null ? 0L : HOURS.toSeconds( parseLong( s ) );
+    }
+
+    static long fromMinutes( Matcher matcher )
+    {
+        String s = matcher.group( 5 );
+        return s == null ? 0L : MINUTES.toSeconds( parseLong( s ) );
+    }
+
+    static long fromSeconds( Matcher matcher )
+    {
+        String s = matcher.group( 6 );
+        return s == null ? 0L : parseLong( s );
+    }
+
+    private static void checkValid( Scanner scanner )
+            throws IOException
+    {
+        IOException exception = scanner.ioException();
+        if ( exception != null )
+        {
+            throw exception;
+        }
+    }
+
+    /**
+     * Reads standard output from {@link Process}.
+     * <br>
+     * The artifact maven-shared-utils has non-daemon Threads which is an issue in Surefire to satisfy System.exit.
+     * This implementation is taylor made without using any Thread.
+     * It's easy to destroy Process from other Thread.
+     */
+    private abstract class ProcessInfoConsumer
+    {
+        abstract ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo );
+
+        ProcessInfo execute( String... command )
+        {
+            ProcessBuilder processBuilder = new ProcessBuilder( command );
+            processBuilder.redirectErrorStream( true );
+            Process process = null;
+            ProcessInfo processInfo = INVALID_PROCESS_INFO;
+            try
+            {
+                process = processBuilder.start();
+                destroyableCommands.add( process );
+                Scanner scanner = new Scanner( process.getInputStream() );
+                while ( scanner.hasNextLine() )
+                {
+                    String line = scanner.nextLine().trim();
+                    processInfo = consumeLine( line, processInfo );
+                }
+                checkValid( scanner );
+                int exitCode = process.waitFor();
+                return exitCode == 0 ? processInfo : ERR_PROCESS_INFO;
+            }
+            catch ( IOException e )
+            {
+                return ERR_PROCESS_INFO;
+            }
+            catch ( InterruptedException e )
+            {
+                return ERR_PROCESS_INFO;
+            }
+            finally
+            {
+                if ( process != null )
+                {
+                    destroyableCommands.remove( process );
+                    process.destroy();
+                    closeQuietly( process.getInputStream() );
+                    closeQuietly( process.getErrorStream() );
+                    closeQuietly( process.getOutputStream() );
+                }
+            }
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0103691a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java
new file mode 100644
index 0000000..7844a2c
--- /dev/null
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java
@@ -0,0 +1,103 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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 static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull;
+
+/**
+ * Immutable object which encapsulates PID and elapsed time (Unix) or start time (Windows).
+ * <br>
+ * Methods
+ * ({@link #getPID()}, {@link #getTime()}, {@link #isTimeAfter(ProcessInfo)}, {@link #isTimeEqualTo(ProcessInfo)})
+ * throw {@link IllegalStateException} if {@link #isValid()} returns {@code false}.
+ *
+ * @author <a href="mailto:tibordigana@apache.org">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+final class ProcessInfo
+{
+    static final ProcessInfo INVALID_PROCESS_INFO = new ProcessInfo( null, null );
+    static final ProcessInfo ERR_PROCESS_INFO = new ProcessInfo( null, null );
+
+    /**
+     * On Unix we do not get PID due to the command is interested only to etime of PPID:
+     * <br>
+     * <pre>/bin/ps -o etime= -p 123</pre>
+     */
+    static ProcessInfo unixProcessInfo( long pid, long etime )
+    {
+        return new ProcessInfo( pid, etime );
+    }
+
+    static ProcessInfo windowsProcessInfo( long pid, String startTimestamp )
+    {
+        return new ProcessInfo( pid, requireNonNull( startTimestamp, "startTimestamp is NULL" ) );
+    }
+
+    private final Long pid;
+    private final Comparable time;
+
+    private ProcessInfo( Long pid, Comparable time )
+    {
+        this.pid = pid;
+        this.time = time;
+    }
+
+    boolean isValid()
+    {
+        return this != INVALID_PROCESS_INFO && this != ERR_PROCESS_INFO;
+    }
+
+    long getPID()
+    {
+        checkValid();
+        return pid;
+    }
+
+    Comparable getTime()
+    {
+        checkValid();
+        return time;
+    }
+
+    @SuppressWarnings( "unchecked" )
+    boolean isTimeEqualTo( ProcessInfo that )
+    {
+        checkValid();
+        that.checkValid();
+        return this.time.compareTo( that.time ) == 0;
+    }
+
+    @SuppressWarnings( "unchecked" )
+    boolean isTimeAfter( ProcessInfo that )
+    {
+        checkValid();
+        that.checkValid();
+        return this.time.compareTo( that.time ) > 0;
+    }
+
+    private void checkValid()
+    {
+        if ( !isValid() )
+        {
+            throw new IllegalStateException( "invalid process info" );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0103691a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
----------------------------------------------------------------------
diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
index 41b4850..d94be71 100644
--- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
+++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java
@@ -70,6 +70,12 @@ public class PropertiesWrapper
         return Integer.parseInt( properties.get( propertyName ) );
     }
 
+    public Long getLongProperty( String propertyName )
+    {
+        String number = getProperty( propertyName );
+        return number == null ? null : Long.parseLong( number );
+    }
+
     public File getFileProperty( String key )
     {
         final String property = getProperty( key );


Mime
View raw message