Repository: maven-surefire
Updated Branches:
  refs/heads/master 58a4dad81 -> f1f4718e5


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/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..f7eb1df
--- /dev/null
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java
@@ -0,0 +1,295 @@
+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:[email protected]";>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() && 
!pluginProcessInfo.isError();
+    }
+
+    /**
+     * 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.isError() )
+            {
+                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 pluginProcessInfo.isValid()
+                           && ( previousPluginProcessInfo == null
+                                        || pluginProcessInfo.isTimeEqualTo( 
previousPluginProcessInfo ) );
+        }
+        else if ( IS_OS_UNIX )
+        {
+            ProcessInfo previousPluginProcessInfo = pluginProcessInfo;
+            pluginProcessInfo = unix();
+            if ( isStopped() || pluginProcessInfo.isError() )
+            {
+                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 pluginProcessInfo.isValid()
+                           && ( 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 : INVALID_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/f1f4718e/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..212e221
--- /dev/null
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java
@@ -0,0 +1,109 @@
+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} or {@link #isError()} returns 
{@code true}.
+ *
+ * @author <a href="mailto:[email protected]";>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;
+    }
+
+    boolean isError()
+    {
+        return 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() || isError() )
+        {
+            throw new IllegalStateException( "invalid process info" );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/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 );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
new file mode 100644
index 0000000..ccdb6e6
--- /dev/null
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
@@ -0,0 +1,181 @@
+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 org.apache.maven.surefire.util.ReflectionUtils;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.lang.management.ManagementFactory;
+import java.lang.reflect.Method;
+
+import static java.lang.Thread.currentThread;
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_FREE_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_LINUX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_NET_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_OPEN_BSD;
+import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodChain;
+import static org.apache.maven.surefire.util.ReflectionUtils.tryLoadClass;
+
+/**
+ * JDK 9 support.
+ *
+ * @author <a href="mailto:[email protected]";>Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public final class SystemUtils
+{
+    private static final int PROC_STATUS_PID_FIRST_CHARS = 20;
+
+    public SystemUtils()
+    {
+        throw new IllegalStateException( "no instantiable constructor" );
+    }
+
+    public static ClassLoader platformClassLoader()
+    {
+        if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+        {
+            return reflectClassLoader( ClassLoader.class, 
"getPlatformClassLoader" );
+        }
+        return null;
+    }
+
+    public static Long pid()
+    {
+        if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+        {
+            Long pid = pidOnJava9();
+            if ( pid != null )
+            {
+                return pid;
+            }
+        }
+
+        if ( IS_OS_LINUX )
+        {
+            try
+            {
+                return pidStatusOnLinux();
+            }
+            catch ( Exception e )
+            {
+                // examine PID via JMX
+            }
+        }
+        else if ( IS_OS_FREE_BSD || IS_OS_NET_BSD || IS_OS_OPEN_BSD )
+        {
+            try
+            {
+                return pidStatusOnBSD();
+            }
+            catch ( Exception e )
+            {
+                // examine PID via JMX
+            }
+        }
+
+        return pidOnJMX();
+    }
+
+    static Long pidOnJMX()
+    {
+        String processName = ManagementFactory.getRuntimeMXBean().getName();
+        if ( processName.contains( "@" ) )
+        {
+            String pid = processName.substring( 0, processName.indexOf( '@' ) 
).trim();
+            try
+            {
+                return Long.parseLong( pid );
+            }
+            catch ( NumberFormatException e )
+            {
+                return null;
+            }
+        }
+
+        return null;
+    }
+
+    static Long pidStatusOnLinux() throws Exception
+    {
+        FileReader input = new FileReader( "/proc/self/stat" );
+        try
+        {
+            // Reading and encoding 20 characters is bit faster than whole 
line.
+            // size of (long) = 19, + 1 space
+            char[] buffer = new char[PROC_STATUS_PID_FIRST_CHARS];
+            String startLine = new String( buffer, 0, input.read( buffer ) );
+            return Long.parseLong( startLine.substring( 0, startLine.indexOf( 
' ' ) ) );
+        }
+        finally
+        {
+            input.close();
+        }
+    }
+
+    /**
+     * The process status.  This file is read-only and returns a single
+     * line containing multiple space-separated fields.
+     * See <a 
href="https://www.freebsd.org/cgi/man.cgi?query=procfs&sektion=5";>procfs 
status</a>
+     *
+     * @return current PID
+     * @throws Exception if could not read /proc/curproc/status
+     */
+    static Long pidStatusOnBSD() throws Exception
+    {
+        BufferedReader input = new BufferedReader( new FileReader( 
"/proc/curproc/status" ) );
+        try
+        {
+            String line = input.readLine();
+            int i1 = 1 + line.indexOf( ' ' );
+            int i2 = line.indexOf( ' ', i1 );
+            return Long.parseLong( line.substring( i1, i2 ) );
+        }
+        finally
+        {
+            input.close();
+        }
+    }
+
+    static Long pidOnJava9()
+    {
+        ClassLoader classLoader = currentThread().getContextClassLoader();
+        Class<?> processHandle = tryLoadClass( classLoader, 
"java.lang.ProcessHandle" );
+        Class<?>[] classesChain = { processHandle, processHandle };
+        String[] methodChain = { "current", "pid" };
+        return (Long) invokeMethodChain( classesChain, methodChain, null );
+    }
+
+    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;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
index 2bdcf21..f073a8b 100644
--- 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
@@ -34,7 +34,9 @@ import org.junit.runners.Suite;
     ClasspathTest.class,
     CommandReaderTest.class,
     PropertiesWrapperTest.class,
-    SurefireReflectorTest.class
+    SurefireReflectorTest.class,
+    PpidCheckerTest.class,
+    SystemUtilsTest.class
 } )
 @RunWith( Suite.class )
 public class JUnit4SuiteTest

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
index 4342ec7..4cf9424 100644
--- 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
@@ -34,10 +34,19 @@ import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.Statement;
 import org.junit.runners.model.TestClass;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.annotation.Annotation;
+import java.net.MalformedURLException;
+import java.net.URL;
 import java.net.URLClassLoader;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.List;
 
+import static java.io.File.pathSeparator;
+import static org.apache.commons.io.FileUtils.readFileToString;
+
 /**
  * JUnit runner testing methods in a separate class loader.
  *
@@ -185,7 +194,63 @@ public class NewClassLoaderRunner
     {
         public TestClassLoader()
         {
-            super( ( (URLClassLoader) 
Thread.currentThread().getContextClassLoader() ).getURLs(), null );
+            super( toClassPath(), null );
+        }
+
+        /**
+         * Compliant with Java 9 or prior version of JRE.
+         *
+         * @return classpath
+         */
+        private static URL[] toClassPath()
+        {
+            try
+            {
+                Collection<URL> cp = toPathList(); // if Maven run
+                if ( cp.isEmpty() )
+                {
+                    // if IDE
+                    cp = toPathList( System.getProperty( "java.class.path" ) );
+                }
+                return cp.toArray( new URL[cp.size()] );
+            }
+            catch ( IOException e )
+            {
+                return new URL[0];
+            }
+        }
+
+        private static Collection<URL> toPathList( String path ) throws 
MalformedURLException
+        {
+            Collection<URL> classPath = new HashSet<URL>();
+            for ( String file : path.split( pathSeparator ) )
+            {
+                classPath.add( new File( file ).toURL() );
+            }
+            return classPath;
+        }
+
+        private static Collection<URL> toPathList()
+        {
+            Collection<URL> classPath = new HashSet<URL>();
+            try
+            {
+                String[] files = readFileToString( new File( 
"target/test-classpath/cp.txt" ) ).split( pathSeparator );
+                for ( String file : files )
+                {
+                    File f = new File( file );
+                    File dir = f.getParentFile();
+                    classPath.add( ( dir.getName().equals( "target" ) ? new 
File( dir, "classes" ) : f ).toURL() );
+                }
+                classPath.add( new File( "target/classes" ).toURL() );
+                classPath.add( new File( "target/test-classes" ).toURL() );
+            }
+            catch ( IOException e )
+            {
+                // turn to java.class.path
+                classPath.clear();
+            }
+            return classPath;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java
new file mode 100644
index 0000000..b0153ac
--- /dev/null
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java
@@ -0,0 +1,144 @@
+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 org.junit.Test;
+
+import java.lang.management.ManagementFactory;
+import java.util.regex.Matcher;
+
+import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Testing {@link PpidChecker} on a platform.
+ *
+ * @author <a href="mailto:[email protected]";>Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class PpidCheckerTest
+{
+    @Test
+    public void shouldHavePpidAsWindows()
+    {
+        assumeTrue( IS_OS_WINDOWS );
+
+        long expectedPid = Long.parseLong( 
ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim() );
+
+        PpidChecker checker = new PpidChecker( expectedPid );
+        ProcessInfo processInfo = checker.windows();
+
+        assertThat( processInfo )
+                .isNotNull();
+
+        assertThat( checker.canUse() )
+                .isTrue();
+
+        assertThat( checker.isProcessAlive() )
+                .isTrue();
+
+        assertThat( processInfo.getPID() )
+                .isEqualTo( expectedPid );
+
+        assertThat( processInfo.getTime() )
+                .isNotNull();
+    }
+
+    @Test
+    public void shouldHavePpidAsUnix()
+    {
+        assumeTrue( IS_OS_UNIX );
+
+        long expectedPid = Long.parseLong( 
ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim() );
+
+        PpidChecker checker = new PpidChecker( expectedPid );
+        ProcessInfo processInfo = checker.unix();
+
+        assertThat( processInfo )
+                .isNotNull();
+
+        assertThat( checker.canUse() )
+                .isTrue();
+
+        assertThat( checker.isProcessAlive() )
+                .isTrue();
+
+        assertThat( processInfo.getPID() )
+                .isEqualTo( expectedPid );
+
+        assertThat( processInfo.getTime() )
+                .isNotNull();
+    }
+
+    @Test
+    public void shouldNotFindSuchPID()
+    {
+        PpidChecker checker = new PpidChecker( 1000000L );
+        assertThat( checker.canUse() )
+                .isTrue();
+
+        boolean isAlive = checker.isProcessAlive();
+
+        assertThat( isAlive )
+                .isFalse();
+    }
+
+    @Test
+    public void shouldParseEtime()
+    {
+        Matcher m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "38" );
+        assertThat( m.matches() )
+                .isFalse();
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "00:05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "01:05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 3600L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+
+        m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "02-01:05:38" );
+        assertThat( m.matches() )
+                .isTrue();
+        assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 2 * 24 * 3600L );
+        assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 3600L );
+        assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L );
+        assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SystemUtilsTest.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SystemUtilsTest.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SystemUtilsTest.java
new file mode 100644
index 0000000..12aff99
--- /dev/null
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/SystemUtilsTest.java
@@ -0,0 +1,131 @@
+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 org.junit.Test;
+
+import java.lang.management.ManagementFactory;
+
+import static org.apache.commons.lang3.JavaVersion.JAVA_9;
+import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_FREE_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_LINUX;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_NET_BSD;
+import static org.apache.commons.lang3.SystemUtils.IS_OS_OPEN_BSD;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+/**
+ * Test of {@link SystemUtils}.
+ *
+ * @author <a href="mailto:[email protected]";>Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+public class SystemUtilsTest
+{
+    @Test
+    public void shouldBePlatformClassLoader()
+    {
+        ClassLoader cl = SystemUtils.platformClassLoader();
+        if ( JAVA_RECENT.atLeast( JAVA_9 ) )
+        {
+            assertThat( cl ).isNotNull();
+        }
+        else
+        {
+            assertThat( cl ).isNull();
+        }
+    }
+
+    @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() );
+    }
+
+    @Test
+    public void shouldBePidOnJigsaw()
+    {
+        assumeTrue( JAVA_RECENT.atLeast( JAVA_9 ) );
+
+        Long actualPid = SystemUtils.pidOnJava9();
+        String expectedPid = 
ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+        assertThat( actualPid + "" )
+                .isEqualTo( expectedPid );
+    }
+
+    @Test
+    public void shouldBePidStatusOnLinux() throws Exception
+    {
+        assumeTrue( IS_OS_LINUX );
+
+        Long actualPid = SystemUtils.pidStatusOnLinux();
+        String expectedPid = 
ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+        assertThat( actualPid + "" )
+                .isEqualTo( expectedPid );
+    }
+
+    @Test
+    public void shouldBePidStatusOnBSD() throws Exception
+    {
+        assumeTrue( IS_OS_FREE_BSD || IS_OS_NET_BSD || IS_OS_OPEN_BSD );
+
+        Long actualPid = SystemUtils.pidStatusOnBSD();
+        String expectedPid = 
ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+        assertThat( actualPid + "" )
+                .isEqualTo( expectedPid );
+    }
+
+    @Test
+    public void shouldBePidOnJMX()
+    {
+        Long actualPid = SystemUtils.pidOnJMX();
+        String expectedPid = 
ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+        assertThat( actualPid + "" )
+                .isEqualTo( expectedPid );
+    }
+
+    @Test
+    public void shouldBePid()
+    {
+        Long actualPid = SystemUtils.pid();
+        String expectedPid = 
ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim();
+
+        assertThat( actualPid + "" )
+                .isEqualTo( expectedPid );
+    }
+
+    public static ClassLoader getPlatformClassLoader()
+    {
+        return ClassLoader.getSystemClassLoader();
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f1f4718e/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java
----------------------------------------------------------------------
diff --git 
a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java
 
b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java
index 1fa88f6..f051c1c 100644
--- 
a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java
+++ 
b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java
@@ -27,8 +27,9 @@ import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Iterator;
-import java.util.Locale;
 
+import static org.apache.commons.lang.SystemUtils.IS_OS_LINUX;
+import static org.apache.commons.lang.SystemUtils.IS_OS_MAC_OSX;
 import static org.fest.assertions.Assertions.assertThat;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
@@ -46,8 +47,7 @@ public class Surefire1295AttributeJvmCrashesToTestsIT
     @Before
     public void skipWindows()
     {
-        String os = System.getProperty( "os.name" ).toLowerCase( Locale.ROOT );
-        assumeTrue( os.equals( "mac os x" ) || os.equals( "linux" ) /*||  
os.contains( "windows" )*/ );
+        assumeTrue( IS_OS_LINUX || IS_OS_MAC_OSX );
     }
 
     @Test

Reply via email to