[SUREFIRE-1262] Add modulepath support

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

Branch: refs/heads/SUREFIRE-1262_3
Commit: b436a15ece40ae4f3eec7612ffbd665ec70200fb
Parents: 660a4cf
Author: Tibor17 <tibordig...@apache.org>
Authored: Fri Nov 17 15:09:52 2017 +0100
Committer: Tibor17 <tibordig...@apache.org>
Committed: Sat Nov 18 01:16:38 2017 +0100

----------------------------------------------------------------------
 maven-surefire-common/pom.xml                   |  27 +-
 .../plugin/surefire/AbstractSurefireMojo.java   | 271 ++++++--
 .../surefire/InPluginVMSurefireStarter.java     |   5 +-
 .../AbstractClasspathForkConfiguration.java     |  73 +++
 .../surefire/booterclient/BooterSerializer.java |  94 +--
 .../ClasspathForkConfiguration.java             |  59 ++
 .../booterclient/DefaultForkConfiguration.java  | 344 ++++++++++
 .../booterclient/ForkConfiguration.java         | 367 +----------
 .../surefire/booterclient/ForkStarter.java      |  27 +-
 .../JarManifestForkConfiguration.java           | 139 ++++
 .../ModularClasspathForkConfiguration.java      | 230 +++++++
 .../plugin/surefire/booterclient/Platform.java  |  29 +-
 .../maven/plugin/surefire/util/Relocator.java   |  39 +-
 .../surefire/providerapi/ServiceLoader.java     |   2 +-
 .../surefire/AbstractSurefireMojoTest.java      | 641 ++++++++++++++++++-
 ...terDeserializerStartupConfigurationTest.java |   8 +-
 .../booterclient/ForkConfigurationTest.java     |  52 +-
 .../maven/surefire/util/RelocatorTest.java      |   6 +-
 .../plugin/surefire/SurefirePluginTest.java     |   3 +-
 pom.xml                                         |  65 +-
 .../maven/surefire/util/DefaultScanResult.java  |  24 +-
 .../maven/surefire/util/ScanResultTest.java     |   8 +-
 .../booter/AbstractPathConfiguration.java       | 115 ++++
 .../apache/maven/surefire/booter/Classpath.java |  19 +-
 .../surefire/booter/ClasspathConfiguration.java |  73 +--
 .../maven/surefire/booter/ForkedBooter.java     |  22 +-
 .../maven/surefire/booter/ModularClasspath.java |  70 ++
 .../booter/ModularClasspathConfiguration.java   |  62 ++
 .../surefire/booter/StartupConfiguration.java   |  28 +-
 .../maven/surefire/booter/SystemUtilsTest.java  |   5 +-
 .../apache/maven/surefire/its/ModulePathIT.java |  45 ++
 .../src/test/resources/modulepath/pom.xml       |  45 ++
 .../modulepath/src/main/java/com/app/Main.java  |  34 +
 .../modulepath/src/main/java/module-info.java   |  21 +
 .../src/test/java/com/app/AppTest.java          |  36 ++
 35 files changed, 2438 insertions(+), 650 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/pom.xml
----------------------------------------------------------------------
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index 1bcd7c5..7e063c8 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -97,25 +97,36 @@
     <dependency>
       <groupId>org.apache.maven.shared</groupId>
       <artifactId>maven-common-artifact-filters</artifactId>
-      <version>1.3</version>
-      <exclusions>
-        <exclusion>
-          <groupId>org.apache.maven.shared</groupId>
-          <artifactId>maven-plugin-testing-harness</artifactId>
-        </exclusion>
-      </exclusions>
     </dependency>
     <dependency>
       <groupId>org.fusesource.jansi</groupId>
       <artifactId>jansi</artifactId>
-      <version>1.13</version>
       <scope>provided</scope>
     </dependency>
     <dependency>
+      <groupId>org.codehaus.plexus</groupId>
+      <artifactId>plexus-java</artifactId>
+    </dependency>
+    <dependency>
       <groupId>org.mockito</groupId>
       <artifactId>mockito-core</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-module-junit4</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.powermock</groupId>
+      <artifactId>powermock-api-mockito2</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index 7aa1e37..16b59ca 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -24,6 +24,7 @@ import org.apache.maven.artifact.Artifact;
 import org.apache.maven.artifact.factory.ArtifactFactory;
 import org.apache.maven.artifact.metadata.ArtifactMetadataSource;
 import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.resolver.AbstractArtifactResolutionException;
 import org.apache.maven.artifact.resolver.ArtifactNotFoundException;
 import org.apache.maven.artifact.resolver.ArtifactResolutionException;
 import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
@@ -43,6 +44,9 @@ import org.apache.maven.plugin.descriptor.PluginDescriptor;
 import org.apache.maven.plugin.surefire.booterclient.ChecksumCalculator;
 import org.apache.maven.plugin.surefire.booterclient.ForkConfiguration;
 import org.apache.maven.plugin.surefire.booterclient.ForkStarter;
+import 
org.apache.maven.plugin.surefire.booterclient.ClasspathForkConfiguration;
+import 
org.apache.maven.plugin.surefire.booterclient.JarManifestForkConfiguration;
+import 
org.apache.maven.plugin.surefire.booterclient.ModularClasspathForkConfiguration;
 import org.apache.maven.plugin.surefire.booterclient.Platform;
 import org.apache.maven.plugin.surefire.booterclient.ProviderDetector;
 import org.apache.maven.plugin.surefire.log.PluginConsoleLogger;
@@ -58,6 +62,8 @@ import 
org.apache.maven.surefire.booter.ClassLoaderConfiguration;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
 import org.apache.maven.surefire.booter.KeyValueSource;
+import org.apache.maven.surefire.booter.ModularClasspath;
+import org.apache.maven.surefire.booter.ModularClasspathConfiguration;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
 import org.apache.maven.surefire.booter.ProviderParameterNames;
 import org.apache.maven.surefire.booter.Shutdown;
@@ -81,6 +87,9 @@ import org.apache.maven.toolchain.DefaultToolchain;
 import org.apache.maven.toolchain.Toolchain;
 import org.apache.maven.toolchain.ToolchainManager;
 import org.codehaus.plexus.logging.Logger;
+import org.codehaus.plexus.languages.java.jpms.LocationManager;
+import org.codehaus.plexus.languages.java.jpms.ResolvePathsRequest;
+import org.codehaus.plexus.languages.java.jpms.ResolvePathsResult;
 
 import javax.annotation.Nonnull;
 import java.io.File;
@@ -88,7 +97,6 @@ import java.io.IOException;
 import java.lang.reflect.Array;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
@@ -99,13 +107,18 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
 import java.util.concurrent.ConcurrentHashMap;
 
 import static java.lang.Thread.currentThread;
+import static java.util.Arrays.asList;
+import static java.util.Collections.addAll;
 import static java.util.Collections.singletonMap;
 import static org.apache.commons.lang3.JavaVersion.JAVA_1_7;
 import static org.apache.commons.lang3.JavaVersion.JAVA_9;
 import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT;
+import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
 import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS;
 import static org.apache.commons.lang3.SystemUtils.isJavaVersionAtLeast;
 import static org.apache.maven.shared.utils.StringUtils.capitalizeFirstLetter;
@@ -133,12 +146,13 @@ public abstract class AbstractSurefireMojo
     extends AbstractMojo
     implements SurefireExecutionParameters
 {
+    private static final String FORK_ONCE = "once";
+    private static final String FORK_ALWAYS = "always";
+    private static final String FORK_NEVER = "never";
+    private static final String FORK_PERTHREAD = "perthread";
     private static final Map<String, String> JAVA_9_MATCHER_OLD_NOTATION = 
singletonMap( "version", "[1.9,)" );
-
     private static final Map<String, String> JAVA_9_MATCHER = singletonMap( 
"version", "[9,)" );
-
     private static final Platform PLATFORM = new Platform();
-
     private static final File SYSTEM_TMP_DIR = new File( System.getProperty( 
"java.io.tmpdir" ) );
 
     private final ProviderDetector providerDetector = new ProviderDetector();
@@ -731,6 +745,9 @@ public abstract class AbstractSurefireMojo
     @Component
     private ToolchainManager toolchainManager;
 
+    @Component
+    private LocationManager locationManager;
+
     private Artifact surefireBooterArtifact;
 
     private Toolchain toolchain;
@@ -887,7 +904,7 @@ public abstract class AbstractSurefireMojo
                 // @TODO noinspection unchecked, check MavenProject 3.x for 
Generics in surefire:3.0
                 @SuppressWarnings( "unchecked" )
                 List<File> dependenciesToScan =
-                    DependencyScanner.filter( project.getTestArtifacts(), 
Arrays.asList( getDependenciesToScan() ) );
+                    DependencyScanner.filter( project.getTestArtifacts(), 
asList( getDependenciesToScan() ) );
                 DependencyScanner scanner = new DependencyScanner( 
dependenciesToScan, getIncludedAndExcludedTests() );
                 return scanner.scan();
             }
@@ -1107,7 +1124,7 @@ public abstract class AbstractSurefireMojo
             createCopyAndReplaceForkNumPlaceholder( effectiveProperties, 1 
).copyToSystemProperties();
 
             InPluginVMSurefireStarter surefireStarter =
-                createInprocessStarter( provider, classLoaderConfiguration, 
runOrderParameters );
+                createInprocessStarter( provider, classLoaderConfiguration, 
runOrderParameters, scanResult );
             return surefireStarter.runSuitesInProcess( scanResult );
         }
         else
@@ -1123,7 +1140,7 @@ public abstract class AbstractSurefireMojo
             try
             {
                 forkStarter = createForkStarter( provider, forkConfiguration, 
classLoaderConfiguration,
-                                                       runOrderParameters, 
getConsoleLogger() );
+                                                       runOrderParameters, 
getConsoleLogger(), scanResult );
 
                 return forkStarter.run( effectiveProperties, scanResult );
             }
@@ -1199,6 +1216,16 @@ public abstract class AbstractSurefireMojo
         return tc;
     }
 
+    private boolean existsModuleDescriptor()
+    {
+        return getModuleDescriptor().isFile();
+    }
+
+    private File getModuleDescriptor()
+    {
+        return new File( getClassesDirectory(), "module-info.class" );
+    }
+
     /**
      * Converts old TestNG configuration parameters over to new properties 
based configuration
      * method. (if any are defined the old way)
@@ -1511,7 +1538,7 @@ public abstract class AbstractSurefireMojo
 
     static boolean isForkModeNever( String forkMode )
     {
-        return ForkConfiguration.FORK_NEVER.equals( forkMode );
+        return FORK_NEVER.equals( forkMode );
     }
 
     protected boolean isForking()
@@ -1525,17 +1552,17 @@ public abstract class AbstractSurefireMojo
 
         if ( toolchain != null && isForkModeNever( forkMode1 ) )
         {
-            return ForkConfiguration.FORK_ONCE;
+            return FORK_ONCE;
         }
 
-        return ForkConfiguration.getEffectiveForkMode( forkMode1 );
+        return getEffectiveForkMode( forkMode1 );
     }
 
     private List<RunOrder> getRunOrders()
     {
         String runOrderString = getRunOrder();
         RunOrder[] runOrder = runOrderString == null ? RunOrder.DEFAULT : 
RunOrder.valueOfMulti( runOrderString );
-        return Arrays.asList( runOrder );
+        return asList( runOrder );
     }
 
     private boolean requiresRunHistory()
@@ -1649,8 +1676,9 @@ public abstract class AbstractSurefireMojo
         return new File( getBasedir(), ".surefire-" + configurationHash );
     }
 
-    StartupConfiguration createStartupConfiguration( ProviderInfo provider,
-                                                     ClassLoaderConfiguration 
classLoaderConfiguration )
+    private StartupConfiguration createStartupConfiguration( ProviderInfo 
provider, boolean isInprocess,
+                                                             
ClassLoaderConfiguration classLoaderConfiguration,
+                                                             DefaultScanResult 
scanResult )
         throws MojoExecutionException, MojoFailureException
     {
         try
@@ -1669,35 +1697,91 @@ public abstract class AbstractSurefireMojo
                     providerClasspath.addClassPathElementUrl( 
surefireArtifact.getFile().getAbsolutePath() )
                             .addClassPathElementUrl( 
getApiArtifact().getFile().getAbsolutePath() );
 
-            final Classpath testClasspath = generateTestClasspath();
-
-            getConsoleLogger().debug( testClasspath.getLogMessage( "test" ) );
-            getConsoleLogger().debug( providerClasspath.getLogMessage( 
"provider" ) );
+            File moduleDescriptor = getModuleDescriptor();
 
-            getConsoleLogger().debug( testClasspath.getCompactLogMessage( 
"test(compact)" ) );
-            getConsoleLogger().debug( providerClasspath.getCompactLogMessage( 
"provider(compact)" ) );
-
-            final ClasspathConfiguration classpathConfiguration =
-                new ClasspathConfiguration( testClasspath, providerClasspath, 
inprocClassPath,
-                                            effectiveIsEnableAssertions(), 
isChildDelegation() );
-
-            return new StartupConfiguration( providerName, 
classpathConfiguration, classLoaderConfiguration,
-                                             isForking(), false );
+            if ( moduleDescriptor.exists() && !isInprocess )
+            {
+                return newStartupConfigForModularClasspath( 
classLoaderConfiguration, providerClasspath, providerName,
+                        moduleDescriptor, scanResult );
+            }
+            else
+            {
+                return newStartupConfigForNonModularClasspath( 
classLoaderConfiguration, providerClasspath,
+                        inprocClassPath, providerName );
+            }
         }
-        catch ( ArtifactResolutionException e )
+        catch ( AbstractArtifactResolutionException e )
         {
             throw new MojoExecutionException( "Unable to generate classpath: " 
+ e, e );
         }
-        catch ( ArtifactNotFoundException e )
+        catch ( InvalidVersionSpecificationException e )
         {
             throw new MojoExecutionException( "Unable to generate classpath: " 
+ e, e );
         }
-        catch ( InvalidVersionSpecificationException e )
+        catch ( IOException e )
         {
-            throw new MojoExecutionException( "Unable to generate classpath: " 
+ e, e );
+            throw new MojoExecutionException( e.getMessage(), e );
         }
     }
 
+    private StartupConfiguration newStartupConfigForNonModularClasspath(
+            ClassLoaderConfiguration classLoaderConfiguration, Classpath 
providerClasspath, Classpath inprocClasspath,
+            String providerName )
+            throws MojoExecutionException, MojoFailureException, 
InvalidVersionSpecificationException,
+            AbstractArtifactResolutionException
+    {
+        Classpath testClasspath = generateTestClasspath();
+
+        getConsoleLogger().debug( testClasspath.getLogMessage( "test 
classpath:" ) );
+        getConsoleLogger().debug( providerClasspath.getLogMessage( "provider 
classpath:" ) );
+        getConsoleLogger().debug( testClasspath.getCompactLogMessage( 
"test(compact) classpath:" ) );
+        getConsoleLogger().debug( providerClasspath.getCompactLogMessage( 
"provider(compact) classpath:" ) );
+
+        ClasspathConfiguration classpathConfiguration = new 
ClasspathConfiguration( testClasspath, providerClasspath,
+                inprocClasspath, effectiveIsEnableAssertions(), 
isChildDelegation() );
+
+        return new StartupConfiguration( providerName, classpathConfiguration, 
classLoaderConfiguration, isForking(),
+                false );
+    }
+
+    private StartupConfiguration newStartupConfigForModularClasspath( 
ClassLoaderConfiguration classLoaderConfiguration,
+                                                              Classpath 
providerClasspath, String providerName,
+                                                              File 
moduleDescriptor, DefaultScanResult scanResult )
+            throws MojoExecutionException, MojoFailureException, 
InvalidVersionSpecificationException,
+            AbstractArtifactResolutionException, IOException
+    {
+        ResolvePathsRequest<String> req = ResolvePathsRequest.withStrings( 
generateTestClasspath().getClassPath() )
+                .setMainModuleDescriptor( moduleDescriptor.getAbsolutePath() );
+
+        ResolvePathsResult<String> result = locationManager.resolvePaths( req 
);
+
+        Classpath testClasspath = new Classpath( result.getClasspathElements() 
);
+        Classpath testModulepath = new Classpath( 
result.getModulepathElements().keySet() );
+
+        SortedSet<String> packages = new TreeSet<String>();
+
+        for ( String className : scanResult.getClasses() )
+        {
+            packages.add( substringBeforeLast( className, "." ) );
+        }
+
+        ModularClasspath modularClasspath = new ModularClasspath( 
moduleDescriptor, testModulepath.getClassPath(),
+                packages, getTestClassesDirectory() );
+
+        ModularClasspathConfiguration classpathConfiguration = new 
ModularClasspathConfiguration( modularClasspath,
+                testClasspath, providerClasspath, 
effectiveIsEnableAssertions(), isChildDelegation() );
+
+        getConsoleLogger().debug( testClasspath.getLogMessage( "test 
classpath:" ) );
+        getConsoleLogger().debug( testModulepath.getLogMessage( "test 
modulepath:" ) );
+        getConsoleLogger().debug( providerClasspath.getLogMessage( "provider 
classpath:" ) );
+        getConsoleLogger().debug( testClasspath.getCompactLogMessage( 
"test(compact) classpath:" ) );
+        getConsoleLogger().debug( testModulepath.getCompactLogMessage( 
"test(compact) modulepath:" ) );
+        getConsoleLogger().debug( providerClasspath.getCompactLogMessage( 
"provider(compact) classpath:" ) );
+
+        return new StartupConfiguration( providerName, classpathConfiguration, 
classLoaderConfiguration, isForking(),
+                false );
+    }
+
     private Artifact getCommonArtifact()
     {
         return getPluginArtifactMap().get( 
"org.apache.maven.surefire:maven-surefire-common" );
@@ -1800,7 +1884,7 @@ public abstract class AbstractSurefireMojo
         if ( isSpecificTestSpecified() )
         {
             includes = new ArrayList<String>();
-            Collections.addAll( includes, split( getTest(), "," ) );
+            addAll( includes, split( getTest(), "," ) );
         }
         else
         {
@@ -1822,7 +1906,7 @@ public abstract class AbstractSurefireMojo
 
             if ( includes == null || includes.isEmpty() )
             {
-                includes = Arrays.asList( getDefaultIncludes() );
+                includes = asList( getDefaultIncludes() );
             }
         }
 
@@ -1941,11 +2025,13 @@ public abstract class AbstractSurefireMojo
     }
 
     private ForkStarter createForkStarter( ProviderInfo provider, 
ForkConfiguration forkConfiguration,
-                                             ClassLoaderConfiguration 
classLoaderConfiguration,
-                                             RunOrderParameters 
runOrderParameters, ConsoleLogger log )
+                                           ClassLoaderConfiguration 
classLoaderConfiguration,
+                                           RunOrderParameters 
runOrderParameters, ConsoleLogger log,
+                                           DefaultScanResult scanResult )
         throws MojoExecutionException, MojoFailureException
     {
-        StartupConfiguration startupConfiguration = 
createStartupConfiguration( provider, classLoaderConfiguration );
+        StartupConfiguration startupConfiguration =
+                createStartupConfiguration( provider, false, 
classLoaderConfiguration, scanResult );
         String configChecksum = getConfigChecksum();
         StartupReportConfiguration startupReportConfiguration = 
getStartupReportConfiguration( configChecksum );
         ProviderConfiguration providerConfiguration = 
createProviderConfiguration( runOrderParameters );
@@ -1954,16 +2040,18 @@ public abstract class AbstractSurefireMojo
     }
 
     private InPluginVMSurefireStarter createInprocessStarter( ProviderInfo 
provider,
-                                                                
ClassLoaderConfiguration classLoaderConfiguration,
-                                                                
RunOrderParameters runOrderParameters )
+                                                              
ClassLoaderConfiguration classLoaderConfiguration,
+                                                              
RunOrderParameters runOrderParameters,
+                                                              
DefaultScanResult scanResult )
         throws MojoExecutionException, MojoFailureException
     {
-        StartupConfiguration startupConfiguration = 
createStartupConfiguration( provider, classLoaderConfiguration );
+        StartupConfiguration startupConfiguration =
+                createStartupConfiguration( provider, true, 
classLoaderConfiguration, scanResult );
         String configChecksum = getConfigChecksum();
         StartupReportConfiguration startupReportConfiguration = 
getStartupReportConfiguration( configChecksum );
         ProviderConfiguration providerConfiguration = 
createProviderConfiguration( runOrderParameters );
-        return new InPluginVMSurefireStarter( startupConfiguration, 
providerConfiguration,
-                                                    
startupReportConfiguration, consoleLogger );
+        return new InPluginVMSurefireStarter( startupConfiguration, 
providerConfiguration, startupReportConfiguration,
+                                              getConsoleLogger() );
     }
 
     private ForkConfiguration getForkConfiguration() throws 
MojoFailureException
@@ -1973,36 +2061,76 @@ public abstract class AbstractSurefireMojo
         Artifact shadeFire = getPluginArtifactMap().get( 
"org.apache.maven.surefire:surefire-shadefire" );
 
         // todo: 150 milli seconds, try to fetch List<String> within classpath 
asynchronously
-        final Classpath bootClasspathConfiguration =
-            getArtifactClasspath( shadeFire != null ? shadeFire : 
surefireBooterArtifact );
-
-        return new ForkConfiguration( bootClasspathConfiguration, tmpDir, 
getEffectiveDebugForkedProcess(),
-                                      getEffectiveJvm(),
-                                      getWorkingDirectory() != null ? 
getWorkingDirectory() : getBasedir(),
-                                      getProject().getModel().getProperties(),
-                                      getArgLine(), getEnvironmentVariables(), 
getConsoleLogger().isDebugEnabled(),
-                                      getEffectiveForkCount(), reuseForks, 
PLATFORM );
+        Classpath bootClasspath = getArtifactClasspath( shadeFire != null ? 
shadeFire : surefireBooterArtifact );
+
+        Platform platform = PLATFORM.withJdkExecAttributesForTests( 
getEffectiveJvm() );
+
+        if ( platform.getJdkExecAttributesForTests().isJava9AtLeast() && 
existsModuleDescriptor() )
+        {
+            return new ModularClasspathForkConfiguration( bootClasspath,
+                    tmpDir,
+                    getEffectiveDebugForkedProcess(),
+                    getWorkingDirectory() != null ? getWorkingDirectory() : 
getBasedir(),
+                    getProject().getModel().getProperties(),
+                    getArgLine(),
+                    getEnvironmentVariables(),
+                    getConsoleLogger().isDebugEnabled(),
+                    getEffectiveForkCount(),
+                    reuseForks,
+                    platform,
+                    getConsoleLogger() );
+        }
+        else if ( 
getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable() )
+        {
+            return new JarManifestForkConfiguration( bootClasspath,
+                    tmpDir,
+                    getEffectiveDebugForkedProcess(),
+                    getWorkingDirectory() != null ? getWorkingDirectory() : 
getBasedir(),
+                    getProject().getModel().getProperties(),
+                    getArgLine(),
+                    getEnvironmentVariables(),
+                    getConsoleLogger().isDebugEnabled(),
+                    getEffectiveForkCount(),
+                    reuseForks,
+                    platform,
+                    getConsoleLogger() );
+        }
+        else
+        {
+            return new ClasspathForkConfiguration( bootClasspath,
+                    tmpDir,
+                    getEffectiveDebugForkedProcess(),
+                    getWorkingDirectory() != null ? getWorkingDirectory() : 
getBasedir(),
+                    getProject().getModel().getProperties(),
+                    getArgLine(),
+                    getEnvironmentVariables(),
+                    getConsoleLogger().isDebugEnabled(),
+                    getEffectiveForkCount(),
+                    reuseForks,
+                    platform,
+                    getConsoleLogger() );
+        }
     }
 
     private void convertDeprecatedForkMode()
     {
         String effectiveForkMode = getEffectiveForkMode();
         // FORK_ONCE (default) is represented by the default values of 
forkCount and reuseForks
-        if ( ForkConfiguration.FORK_PERTHREAD.equals( effectiveForkMode ) )
+        if ( FORK_PERTHREAD.equals( effectiveForkMode ) )
         {
             forkCount = String.valueOf( threadCount );
         }
-        else if ( ForkConfiguration.FORK_NEVER.equals( effectiveForkMode ) )
+        else if ( FORK_NEVER.equals( effectiveForkMode ) )
         {
             forkCount = "0";
         }
-        else if ( ForkConfiguration.FORK_ALWAYS.equals( effectiveForkMode ) )
+        else if ( FORK_ALWAYS.equals( effectiveForkMode ) )
         {
             forkCount = "1";
             reuseForks = false;
         }
 
-        if ( !ForkConfiguration.FORK_ONCE.equals( getForkMode() ) )
+        if ( !FORK_ONCE.equals( getForkMode() ) )
         {
             getConsoleLogger().warning( "The parameter forkMode is deprecated 
since version 2.14. "
                                                 + "Use forkCount and 
reuseForks instead." );
@@ -2263,14 +2391,14 @@ public abstract class AbstractSurefireMojo
         if ( getClasspathDependencyScopeExclude() != null && 
!getClasspathDependencyScopeExclude().isEmpty() )
         {
             ArtifactFilter dependencyFilter = new ScopeArtifactFilter( 
getClasspathDependencyScopeExclude() );
-            classpathArtifacts = this.filterArtifacts( classpathArtifacts, 
dependencyFilter );
+            classpathArtifacts = filterArtifacts( classpathArtifacts, 
dependencyFilter );
         }
 
         if ( getClasspathDependencyExcludes() != null )
         {
-            ArtifactFilter dependencyFilter =
-                new PatternIncludesArtifactFilter( Arrays.asList( 
getClasspathDependencyExcludes() ) );
-            classpathArtifacts = this.filterArtifacts( classpathArtifacts, 
dependencyFilter );
+            List<String> excludedDependencies = asList( 
getClasspathDependencyExcludes() );
+            ArtifactFilter dependencyFilter = new 
PatternIncludesArtifactFilter( excludedDependencies );
+            classpathArtifacts = filterArtifacts( classpathArtifacts, 
dependencyFilter );
         }
 
         for ( Artifact artifact : classpathArtifacts )
@@ -2292,7 +2420,7 @@ public abstract class AbstractSurefireMojo
             {
                 if ( classpathElement != null )
                 {
-                    Collections.addAll( classpath, split( classpathElement, 
"," ) );
+                    addAll( classpath, split( classpathElement, "," ) );
                 }
             }
         }
@@ -2337,7 +2465,7 @@ public abstract class AbstractSurefireMojo
      * @param filter    The filter to apply
      * @return The filtered result
      */
-    private Set<Artifact> filterArtifacts( Set<Artifact> artifacts, 
ArtifactFilter filter )
+    private static Set<Artifact> filterArtifacts( Set<Artifact> artifacts, 
ArtifactFilter filter )
     {
         Set<Artifact> filteredArtifacts = new LinkedHashSet<Artifact>();
 
@@ -2486,7 +2614,7 @@ public abstract class AbstractSurefireMojo
     private void ensureThreadCountWithPerThread()
         throws MojoFailureException
     {
-        if ( ForkConfiguration.FORK_PERTHREAD.equals( getEffectiveForkMode() ) 
&& getThreadCount() < 1 )
+        if ( FORK_PERTHREAD.equals( getEffectiveForkMode() ) && 
getThreadCount() < 1 )
         {
             throw new MojoFailureException( "Fork mode perthread requires a 
thread count" );
         }
@@ -3355,7 +3483,7 @@ public abstract class AbstractSurefireMojo
     {
         if ( getArgLine() != null )
         {
-            List<String> args = Arrays.asList( getArgLine().split( " " ) );
+            List<String> args = asList( getArgLine().split( " " ) );
             if ( args.contains( "-da" ) || args.contains( "-disableassertions" 
) )
             {
                 return false;
@@ -3510,4 +3638,25 @@ public abstract class AbstractSurefireMojo
     {
         this.tempDir = tempDir;
     }
+
+    private static String getEffectiveForkMode( String forkMode )
+    {
+        if ( "pertest".equalsIgnoreCase( forkMode ) )
+        {
+            return FORK_ALWAYS;
+        }
+        else if ( "none".equalsIgnoreCase( forkMode ) )
+        {
+            return FORK_NEVER;
+        }
+        else if ( forkMode.equals( FORK_NEVER ) || forkMode.equals( FORK_ONCE )
+                || forkMode.equals( FORK_ALWAYS ) || forkMode.equals( 
FORK_PERTHREAD ) )
+        {
+            return forkMode;
+        }
+        else
+        {
+            throw new IllegalArgumentException( "Fork mode " + forkMode + " is 
not a legal value" );
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
index b97c192..1d85001 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/InPluginVMSurefireStarter.java
@@ -20,6 +20,7 @@ package org.apache.maven.plugin.surefire;
  */
 
 import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.ClasspathConfiguration;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireExecutionException;
@@ -73,7 +74,9 @@ public class InPluginVMSurefireStarter
         scanResult.writeTo( providerProperties );
 
         startupConfig.writeSurefireTestClasspathProperty();
-        ClassLoader testClassLoader = 
startupConfig.getClasspathConfiguration().createMergedClassLoader();
+        ClassLoader testClassLoader = startupConfig.getClasspathConfiguration()
+                .toRealPath( ClasspathConfiguration.class )
+                .createMergedClassLoader();
 
         CommonReflector surefireReflector = new CommonReflector( 
testClassLoader );
 

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
new file mode 100644
index 0000000..90d1a1d
--- /dev/null
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/AbstractClasspathForkConfiguration.java
@@ -0,0 +1,73 @@
+package org.apache.maven.plugin.surefire.booterclient;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.Classpath;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.21.0.Jigsaw
+ */
+abstract class AbstractClasspathForkConfiguration
+        extends DefaultForkConfiguration
+{
+    private static final String ADD_MODULES = "--add-modules";
+    private static final String ADD_MODULES_VALUE = "java.se.ee";
+    private static final String ALL_JAVA_API = ADD_MODULES + " " + 
ADD_MODULES_VALUE;
+
+    @SuppressWarnings( "checkstyle:parameternumber" )
+    public AbstractClasspathForkConfiguration( @Nonnull Classpath 
bootClasspath,
+                                               @Nonnull File tempDirectory,
+                                               @Nullable String debugLine,
+                                               @Nonnull File workingDirectory,
+                                               @Nonnull Properties 
modelProperties,
+                                               @Nullable String argLine,
+                                               @Nonnull Map<String, String> 
environmentVariables,
+                                               boolean debug,
+                                               int forkCount,
+                                               boolean reuseForks,
+                                               @Nonnull Platform 
pluginPlatform,
+                                               @Nonnull ConsoleLogger log )
+    {
+        super( bootClasspath, tempDirectory, debugLine, workingDirectory, 
modelProperties, argLine,
+                environmentVariables, debug, forkCount, reuseForks, 
pluginPlatform, log );
+    }
+
+    @Override
+    @Nonnull
+    protected String extendJvmArgLine( @Nonnull String jvmArgLine )
+    {
+        if ( getJdkForTests().isJava9AtLeast() && !jvmArgLine.contains( 
ADD_MODULES ) )
+        {
+            return jvmArgLine.isEmpty() ? ALL_JAVA_API : ALL_JAVA_API + " " + 
jvmArgLine;
+        }
+        else
+        {
+            return jvmArgLine;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/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 591e89c..97140ef 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
@@ -19,17 +19,12 @@ package org.apache.maven.plugin.surefire.booterclient;
  * under the License.
  */
 
-import java.io.File;
-import java.io.IOException;
-import java.util.List;
-
 import org.apache.maven.plugin.surefire.SurefireProperties;
+import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.ClassLoaderConfiguration;
-import org.apache.maven.surefire.booter.ClasspathConfiguration;
 import org.apache.maven.surefire.booter.KeyValueSource;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
 import org.apache.maven.surefire.booter.StartupConfiguration;
-import org.apache.maven.surefire.booter.SystemPropertyManager;
 import org.apache.maven.surefire.cli.CommandLineOption;
 import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.testset.DirectoryScannerParameters;
@@ -39,8 +34,40 @@ import org.apache.maven.surefire.testset.TestListResolver;
 import org.apache.maven.surefire.testset.TestRequest;
 import org.apache.maven.surefire.util.RunOrder;
 
-// CHECKSTYLE_OFF: imports
-import static org.apache.maven.surefire.booter.BooterConstants.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+import static 
org.apache.maven.surefire.booter.AbstractPathConfiguration.CHILD_DELEGATION;
+import static 
org.apache.maven.surefire.booter.AbstractPathConfiguration.CLASSPATH;
+import static 
org.apache.maven.surefire.booter.AbstractPathConfiguration.ENABLE_ASSERTIONS;
+import static 
org.apache.maven.surefire.booter.AbstractPathConfiguration.SUREFIRE_CLASSPATH;
+import static 
org.apache.maven.surefire.booter.BooterConstants.EXCLUDES_PROPERTY_PREFIX;
+import static org.apache.maven.surefire.booter.BooterConstants.FAIL_FAST_COUNT;
+import static org.apache.maven.surefire.booter.BooterConstants.FAILIFNOTESTS;
+import static org.apache.maven.surefire.booter.BooterConstants.FORKTESTSET;
+import static 
org.apache.maven.surefire.booter.BooterConstants.FORKTESTSET_PREFER_TESTS_FROM_IN_STREAM;
+import static 
org.apache.maven.surefire.booter.BooterConstants.INCLUDES_PROPERTY_PREFIX;
+import static 
org.apache.maven.surefire.booter.BooterConstants.ISTRIMSTACKTRACE;
+import static 
org.apache.maven.surefire.booter.BooterConstants.MAIN_CLI_OPTIONS;
+import static org.apache.maven.surefire.booter.BooterConstants.PLUGIN_PID;
+import static 
org.apache.maven.surefire.booter.BooterConstants.PROVIDER_CONFIGURATION;
+import static 
org.apache.maven.surefire.booter.BooterConstants.REPORTSDIRECTORY;
+import static org.apache.maven.surefire.booter.BooterConstants.REQUESTEDTEST;
+import static 
org.apache.maven.surefire.booter.BooterConstants.RERUN_FAILING_TESTS_COUNT;
+import static org.apache.maven.surefire.booter.BooterConstants.RUN_ORDER;
+import static 
org.apache.maven.surefire.booter.BooterConstants.RUN_STATISTICS_FILE;
+import static org.apache.maven.surefire.booter.BooterConstants.SHUTDOWN;
+import static 
org.apache.maven.surefire.booter.BooterConstants.SOURCE_DIRECTORY;
+import static 
org.apache.maven.surefire.booter.BooterConstants.SPECIFIC_TEST_PROPERTY_PREFIX;
+import static 
org.apache.maven.surefire.booter.BooterConstants.SYSTEM_EXIT_TIMEOUT;
+import static 
org.apache.maven.surefire.booter.BooterConstants.TEST_CLASSES_DIRECTORY;
+import static 
org.apache.maven.surefire.booter.BooterConstants.TEST_SUITE_XML_FILES;
+import static 
org.apache.maven.surefire.booter.BooterConstants.TESTARTIFACT_CLASSIFIER;
+import static 
org.apache.maven.surefire.booter.BooterConstants.TESTARTIFACT_VERSION;
+import static 
org.apache.maven.surefire.booter.BooterConstants.USEMANIFESTONLYJAR;
+import static 
org.apache.maven.surefire.booter.BooterConstants.USESYSTEMCLASSLOADER;
+import static 
org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
 
 /**
  * Knows how to serialize and deserialize the booter configuration.
@@ -78,11 +105,11 @@ class BooterSerializer
 
         properties.setProperty( PLUGIN_PID, pid );
 
-        ClasspathConfiguration cp = 
providerConfiguration.getClasspathConfiguration();
-        properties.setClasspath( ClasspathConfiguration.CLASSPATH, 
cp.getTestClasspath() );
-        properties.setClasspath( ClasspathConfiguration.SUREFIRE_CLASSPATH, 
cp.getProviderClasspath() );
-        properties.setProperty( ClasspathConfiguration.ENABLE_ASSERTIONS, 
String.valueOf( cp.isEnableAssertions() ) );
-        properties.setProperty( ClasspathConfiguration.CHILD_DELEGATION, 
String.valueOf( cp.isChildDelegation() ) );
+        AbstractPathConfiguration cp = 
providerConfiguration.getClasspathConfiguration();
+        properties.setClasspath( CLASSPATH, cp.getTestClasspath() );
+        properties.setClasspath( SUREFIRE_CLASSPATH, cp.getProviderClasspath() 
);
+        properties.setProperty( ENABLE_ASSERTIONS, toString( 
cp.isEnableAssertions() ) );
+        properties.setProperty( CHILD_DELEGATION, toString( 
cp.isChildDelegation() ) );
 
         TestArtifactInfo testNg = booterConfiguration.getTestArtifact();
         if ( testNg != null )
@@ -101,18 +128,17 @@ class BooterSerializer
             properties.addList( testSuiteDefinition.getSuiteXmlFiles(), 
TEST_SUITE_XML_FILES );
             TestListResolver testFilter = 
testSuiteDefinition.getTestListResolver();
             properties.setProperty( REQUESTEDTEST, testFilter == null ? "" : 
testFilter.getPluginParameterTest() );
-            properties.setNullableProperty( RERUN_FAILING_TESTS_COUNT,
-                                            String.valueOf( 
testSuiteDefinition.getRerunFailingTestsCount() ) );
+            int rerunFailingTestsCount = 
testSuiteDefinition.getRerunFailingTestsCount();
+            properties.setNullableProperty( RERUN_FAILING_TESTS_COUNT, 
toString( rerunFailingTestsCount ) );
         }
 
         DirectoryScannerParameters directoryScannerParameters = 
booterConfiguration.getDirScannerParams();
         if ( directoryScannerParameters != null )
         {
-            properties.setProperty( FAILIFNOTESTS, String.valueOf( 
directoryScannerParameters.isFailIfNoTests() ) );
+            properties.setProperty( FAILIFNOTESTS, toString( 
directoryScannerParameters.isFailIfNoTests() ) );
             properties.addList( directoryScannerParameters.getIncludes(), 
INCLUDES_PROPERTY_PREFIX );
             properties.addList( directoryScannerParameters.getExcludes(), 
EXCLUDES_PROPERTY_PREFIX );
             properties.addList( directoryScannerParameters.getSpecificTests(), 
SPECIFIC_TEST_PROPERTY_PREFIX );
-
             properties.setProperty( TEST_CLASSES_DIRECTORY, 
directoryScannerParameters.getTestClassesDirectory() );
         }
 
@@ -124,46 +150,40 @@ class BooterSerializer
         }
 
         ReporterConfiguration reporterConfiguration = 
booterConfiguration.getReporterConfiguration();
-
         boolean rep = reporterConfiguration.isTrimStackTrace();
         properties.setProperty( ISTRIMSTACKTRACE, rep );
         properties.setProperty( REPORTSDIRECTORY, 
reporterConfiguration.getReportsDirectory() );
         ClassLoaderConfiguration classLoaderConfig = 
providerConfiguration.getClassLoaderConfiguration();
-        properties.setProperty( USESYSTEMCLASSLOADER, String.valueOf( 
classLoaderConfig.isUseSystemClassLoader() ) );
-        properties.setProperty( USEMANIFESTONLYJAR, String.valueOf( 
classLoaderConfig.isUseManifestOnlyJar() ) );
-        properties.setProperty( FAILIFNOTESTS, String.valueOf( 
booterConfiguration.isFailIfNoTests() ) );
+        properties.setProperty( USESYSTEMCLASSLOADER, toString( 
classLoaderConfig.isUseSystemClassLoader() ) );
+        properties.setProperty( USEMANIFESTONLYJAR, toString( 
classLoaderConfig.isUseManifestOnlyJar() ) );
+        properties.setProperty( FAILIFNOTESTS, toString( 
booterConfiguration.isFailIfNoTests() ) );
         properties.setProperty( PROVIDER_CONFIGURATION, 
providerConfiguration.getProviderClassName() );
-        properties.setProperty( FAIL_FAST_COUNT, String.valueOf( 
booterConfiguration.getSkipAfterFailureCount() ) );
+        properties.setProperty( FAIL_FAST_COUNT, toString( 
booterConfiguration.getSkipAfterFailureCount() ) );
         properties.setProperty( SHUTDOWN, 
booterConfiguration.getShutdown().name() );
         List<CommandLineOption> mainCliOptions = 
booterConfiguration.getMainCliOptions();
         if ( mainCliOptions != null )
         {
             properties.addList( mainCliOptions, MAIN_CLI_OPTIONS );
         }
+        properties.setNullableProperty( SYSTEM_EXIT_TIMEOUT, toString( 
booterConfiguration.getSystemExitTimeout() ) );
 
-        properties.setNullableProperty( SYSTEM_EXIT_TIMEOUT,
-                                              String.valueOf( 
booterConfiguration.getSystemExitTimeout() ) );
-
-        return SystemPropertyManager.writePropertiesFile( properties, 
forkConfiguration.getTempDirectory(),
-                                                          "surefire", 
forkConfiguration.isDebug() );
+        File surefireTmpDir = forkConfiguration.getTempDirectory();
+        boolean debug = forkConfiguration.isDebug();
+        return writePropertiesFile( properties, surefireTmpDir, "surefire", 
debug );
     }
 
-    private String getTypeEncoded( Object value )
+    private static String getTypeEncoded( Object value )
     {
         if ( value == null )
         {
             return null;
         }
-        String valueToUse;
-        if ( value instanceof Class )
-        {
-            valueToUse = ( (Class<?>) value ).getName();
-        }
-        else
-        {
-            valueToUse = value.toString();
-        }
+        String valueToUse = value instanceof Class ? ( (Class<?>) value 
).getName() : value.toString();
         return value.getClass().getName() + "|" + valueToUse;
     }
 
+    private static String toString( Object o )
+    {
+        return String.valueOf( o );
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
new file mode 100644
index 0000000..28b966e
--- /dev/null
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ClasspathForkConfiguration.java
@@ -0,0 +1,59 @@
+package org.apache.maven.plugin.surefire.booterclient;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import 
org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.Classpath;
+import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.booter.SurefireBooterForkException;
+
+import java.io.File;
+import java.util.Map;
+import java.util.Properties;
+
+import static org.apache.maven.shared.utils.StringUtils.join;
+
+/**
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.21.0.Jigsaw
+ */
+public final class ClasspathForkConfiguration
+        extends AbstractClasspathForkConfiguration
+{
+    @SuppressWarnings( "checkstyle:parameternumber" )
+    public ClasspathForkConfiguration( Classpath bootClasspath, File 
tempDirectory, String debugLine,
+                                       File workingDirectory, Properties 
modelProperties, String argLine,
+                                       Map<String, String> 
environmentVariables, boolean debug, int forkCount,
+                                       boolean reuseForks, Platform 
pluginPlatform, ConsoleLogger log )
+    {
+        super( bootClasspath, tempDirectory, debugLine, workingDirectory, 
modelProperties, argLine,
+                environmentVariables, debug, forkCount, reuseForks, 
pluginPlatform, log );
+    }
+
+    @Override
+    protected void resolveClasspath( OutputStreamFlushableCommandline cli, 
String booterThatHasMainMethod,
+                                     StartupConfiguration config )
+            throws SurefireBooterForkException
+    {
+        cli.addEnvironment( "CLASSPATH", join( toCompleteClasspath( config 
).iterator(), File.pathSeparator ) );
+        cli.createArg().setValue( booterThatHasMainMethod );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
new file mode 100644
index 0000000..d8dbd3e
--- /dev/null
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/DefaultForkConfiguration.java
@@ -0,0 +1,344 @@
+package org.apache.maven.plugin.surefire.booterclient;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.plugin.surefire.JdkAttributes;
+import 
org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.AbstractPathConfiguration;
+import org.apache.maven.surefire.booter.Classpath;
+import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.booter.SurefireBooterForkException;
+import org.apache.maven.surefire.util.internal.ImmutableMap;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
+import static 
org.apache.maven.plugin.surefire.AbstractSurefireMojo.FORK_NUMBER_PLACEHOLDER;
+import static 
org.apache.maven.plugin.surefire.AbstractSurefireMojo.THREAD_NUMBER_PLACEHOLDER;
+import static org.apache.maven.plugin.surefire.util.Relocator.relocate;
+import static org.apache.maven.surefire.booter.Classpath.join;
+
+/**
+ * Basic framework which constructs CLI.
+ *
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.21.0.Jigsaw
+ */
+public abstract class DefaultForkConfiguration
+        extends ForkConfiguration
+{
+    @Nonnull private final Classpath booterClasspath;
+    @Nonnull private final File tempDirectory;
+    @Nullable
+    private final String debugLine;
+    @Nonnull private final File workingDirectory;
+    @Nonnull private final Properties modelProperties;
+    @Nullable private final String argLine;
+    @Nonnull private final Map<String, String> environmentVariables;
+    private final boolean debug;
+    private final int forkCount;
+    private final boolean reuseForks;
+    @Nonnull private final Platform pluginPlatform;
+    @Nonnull private final ConsoleLogger log;
+
+    @SuppressWarnings( "checkstyle:parameternumber" )
+    public DefaultForkConfiguration( @Nonnull Classpath booterClasspath,
+                                     @Nonnull File tempDirectory,
+                                     @Nullable String debugLine,
+                                     @Nonnull File workingDirectory,
+                                     @Nonnull Properties modelProperties,
+                                     @Nullable String argLine,
+                                     @Nonnull Map<String, String> 
environmentVariables,
+                                     boolean debug,
+                                     int forkCount,
+                                     boolean reuseForks,
+                                     @Nonnull Platform pluginPlatform,
+                                     @Nonnull ConsoleLogger log )
+    {
+        this.booterClasspath = booterClasspath;
+        this.tempDirectory = tempDirectory;
+        this.debugLine = debugLine;
+        this.workingDirectory = workingDirectory;
+        this.modelProperties = modelProperties;
+        this.argLine = argLine;
+        this.environmentVariables = toImmutable( environmentVariables );
+        this.debug = debug;
+        this.forkCount = forkCount;
+        this.reuseForks = reuseForks;
+        this.pluginPlatform = pluginPlatform;
+        this.log = log;
+    }
+
+    protected abstract void resolveClasspath( OutputStreamFlushableCommandline 
cli, String booterThatHasMainMethod,
+                                              StartupConfiguration config )
+            throws SurefireBooterForkException;
+
+    @Nonnull
+    protected String extendJvmArgLine( @Nonnull String jvmArgLine )
+    {
+        return jvmArgLine;
+    }
+
+    /**
+     * @param config       The startup configuration
+     * @param forkNumber   index of forked JVM, to be the replacement in the 
argLine
+     * @return CommandLine able to flush entire command going to be sent to 
forked JVM
+     * @throws org.apache.maven.surefire.booter.SurefireBooterForkException 
when unable to perform the fork
+     */
+    @Nonnull
+    @Override
+    public OutputStreamFlushableCommandline createCommandLine( @Nonnull 
StartupConfiguration config, int forkNumber )
+            throws SurefireBooterForkException
+    {
+        OutputStreamFlushableCommandline cli = new 
OutputStreamFlushableCommandline();
+
+        cli.setWorkingDirectory( getWorkingDirectory( forkNumber 
).getAbsolutePath() );
+
+        for ( Entry<String, String> entry : 
getEnvironmentVariables().entrySet() )
+        {
+            String value = entry.getValue();
+            cli.addEnvironment( entry.getKey(), value == null ? "" : value );
+        }
+
+        cli.setExecutable( getJdkForTests().getJvmExecutable() );
+
+        String jvmArgLine = newJvmArgLine( forkNumber );
+        if ( !jvmArgLine.isEmpty() )
+        {
+            cli.createArg()
+                    .setLine( jvmArgLine );
+        }
+
+        if ( getDebugLine() != null && !getDebugLine().isEmpty() )
+        {
+            cli.createArg()
+                    .setLine( getDebugLine() );
+        }
+
+        resolveClasspath( cli, findStartClass( config ), config );
+
+        return cli;
+    }
+
+    protected List<String> toCompleteClasspath( StartupConfiguration conf ) 
throws SurefireBooterForkException
+    {
+        AbstractPathConfiguration pathConfig = 
conf.getClasspathConfiguration();
+        if ( !( pathConfig.isClassPathConfig() ^ 
pathConfig.isModularPathConfig() ) )
+        {
+            throw new SurefireBooterForkException( "Could not find class-path 
config nor modular class-path either." );
+        }
+
+        //todo this could probably be simplified further
+        Classpath bootClasspath = conf.isProviderMainClass() ? 
pathConfig.getProviderClasspath() : getBooterClasspath();
+        Classpath testClasspath = pathConfig.getTestClasspath();
+        Classpath providerClasspath = pathConfig.getProviderClasspath();
+
+        Classpath completeClasspath = join( join( bootClasspath, testClasspath 
), providerClasspath );
+
+        log.debug( completeClasspath.getLogMessage( "boot classpath:" ) );
+        log.debug( completeClasspath.getCompactLogMessage( "boot(compact) 
classpath:" ) );
+
+        return completeClasspath.getClassPath();
+    }
+
+    @Nonnull
+    protected File getWorkingDirectory( int forkNumber )
+            throws SurefireBooterForkException
+    {
+        File cwd = new File( replaceThreadNumberPlaceholder( 
getWorkingDirectory().getAbsolutePath(), forkNumber ) );
+
+        if ( !cwd.exists() && !cwd.mkdirs() )
+        {
+            throw new SurefireBooterForkException( "Cannot create 
workingDirectory " + cwd.getAbsolutePath() );
+        }
+
+        if ( !cwd.isDirectory() )
+        {
+            throw new SurefireBooterForkException(
+                    "WorkingDirectory " + cwd.getAbsolutePath() + " exists and 
is not a directory" );
+        }
+        return cwd;
+    }
+
+    @Nonnull
+    protected static String replaceThreadNumberPlaceholder( @Nonnull String 
argLine, int threadNumber )
+    {
+        String threadNumberAsString = String.valueOf( threadNumber );
+        return argLine.replace( THREAD_NUMBER_PLACEHOLDER, 
threadNumberAsString )
+                .replace( FORK_NUMBER_PLACEHOLDER, threadNumberAsString );
+    }
+
+    /**
+     * Replaces expressions <pre>@{property-name}</pre> with the corresponding 
properties
+     * from the model. This allows late evaluation of property values when the 
plugin is executed (as compared
+     * to evaluation when the pom is parsed as is done with 
<pre>${property-name}</pre> expressions).
+     *
+     * This allows other plugins to modify or set properties with the changes 
getting picked up by surefire.
+     */
+    @Nonnull
+    protected String interpolateArgLineWithPropertyExpressions()
+    {
+        if ( getArgLine() == null )
+        {
+            return "";
+        }
+
+        String resolvedArgLine = getArgLine().trim();
+
+        if ( resolvedArgLine.isEmpty() )
+        {
+            return "";
+        }
+
+        for ( final String key : getModelProperties().stringPropertyNames() )
+        {
+            String field = "@{" + key + "}";
+            if ( getArgLine().contains( field ) )
+            {
+                resolvedArgLine = resolvedArgLine.replace( field, 
getModelProperties().getProperty( key, "" ) );
+            }
+        }
+
+        return resolvedArgLine;
+    }
+
+    @Nonnull
+    protected static String stripNewLines( @Nonnull String argLine )
+    {
+        return argLine.replace( "\n", " " ).replace( "\r", " " );
+    }
+
+    /**
+     * Immutable map.
+     *
+     * @param map    immutable map copies elements from <code>map</code>
+     * @param <K>    key type
+     * @param <V>    value type
+     * @return never returns null
+     */
+    @Nonnull
+    protected static <K, V> Map<K, V> toImmutable( @Nullable Map<K, V> map )
+    {
+        return map == null ? Collections.<K, V>emptyMap() : new 
ImmutableMap<K, V>( map );
+    }
+
+    @Override
+    @Nonnull
+    public File getTempDirectory()
+    {
+        return tempDirectory;
+    }
+
+    @Override
+    @Nullable
+    protected String getDebugLine()
+    {
+        return debugLine;
+    }
+
+    @Override
+    @Nonnull
+    protected File getWorkingDirectory()
+    {
+        return workingDirectory;
+    }
+
+    @Override
+    @Nonnull
+    protected Properties getModelProperties()
+    {
+        return modelProperties;
+    }
+
+    @Override
+    @Nullable
+    protected String getArgLine()
+    {
+        return argLine;
+    }
+
+    @Override
+    @Nonnull
+    protected Map<String, String> getEnvironmentVariables()
+    {
+        return environmentVariables;
+    }
+
+    @Override
+    protected boolean isDebug()
+    {
+        return debug;
+    }
+
+    @Override
+    protected int getForkCount()
+    {
+        return forkCount;
+    }
+
+    @Override
+    protected boolean isReuseForks()
+    {
+        return reuseForks;
+    }
+
+    @Override
+    @Nonnull
+    protected Platform getPluginPlatform()
+    {
+        return pluginPlatform;
+    }
+
+    @Override
+    @Nonnull
+    protected JdkAttributes getJdkForTests()
+    {
+        return getPluginPlatform().getJdkExecAttributesForTests();
+    }
+
+    @Override
+    @Nonnull
+    protected Classpath getBooterClasspath()
+    {
+        return booterClasspath;
+    }
+
+    //todo add mock unit tests
+    private String newJvmArgLine( int forks )
+    {
+        String interpolatedArgs = stripNewLines( 
interpolateArgLineWithPropertyExpressions() );
+        String argsWithReplacedForkNumbers = replaceThreadNumberPlaceholder( 
interpolatedArgs, forks );
+        return extendJvmArgLine( argsWithReplacedForkNumbers );
+    }
+
+    //todo add mock unit test
+    private static String findStartClass( StartupConfiguration config )
+    {
+        String startClass = config.isProviderMainClass() ? 
config.getActualClassName() : DEFAULT_PROVIDER_CLASS;
+        return config.isShadefire() ? relocate( startClass ) : startClass;
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
index c962424..1ec0fc3 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkConfiguration.java
@@ -19,371 +19,48 @@ package org.apache.maven.plugin.surefire.booterclient;
  * under the License.
  */
 
-import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
 import org.apache.maven.plugin.surefire.JdkAttributes;
 import 
org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
-import org.apache.maven.plugin.surefire.util.Relocator;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ForkedBooter;
 import org.apache.maven.surefire.booter.StartupConfiguration;
 import org.apache.maven.surefire.booter.SurefireBooterForkException;
-import org.apache.maven.surefire.util.internal.ImmutableMap;
 
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 import java.util.Properties;
-import java.util.jar.JarEntry;
-import java.util.jar.JarOutputStream;
-import java.util.jar.Manifest;
-
-import static 
org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
-import static org.apache.maven.shared.utils.StringUtils.join;
 
 /**
  * Configuration for forking tests.
- *
- * @author <a href="mailto:br...@apache.org";>Brett Porter</a>
- * @author <a href="mailto:ken...@apache.org";>Kenney Westerhof</a>
- * @author <a href="mailto:krosenv...@apache.org";>Kristian Rosenvold</a>
  */
-public class ForkConfiguration
+public abstract class ForkConfiguration
 {
-    public static final String FORK_ONCE = "once";
-
-    public static final String FORK_ALWAYS = "always";
-
-    public static final String FORK_NEVER = "never";
-
-    public static final String FORK_PERTHREAD = "perthread";
-
-    private final int forkCount;
-
-    private final boolean reuseForks;
-
-    private final Classpath bootClasspathConfiguration;
-
-    private final JdkAttributes jdk;
-
-    private final Properties modelProperties;
-
-    private final String argLine;
-
-    private final Map<String, String> environmentVariables;
-
-    private final File workingDirectory;
-
-    private final File tempDirectory;
-
-    private final boolean debug;
-
-    private final String debugLine;
-
-    private final Platform pluginPlatform;
-
-    @SuppressWarnings( "checkstyle:parameternumber" )
-    public ForkConfiguration( Classpath bootClasspathConfiguration, File 
tmpDir, String debugLine,
-                              JdkAttributes jdk, File workingDirectory, 
Properties modelProperties, String argLine,
-                              Map<String, String> environmentVariables, 
boolean debugEnabled, int forkCount,
-                              boolean reuseForks, Platform pluginPlatform )
-    {
-        this.bootClasspathConfiguration = bootClasspathConfiguration;
-        this.tempDirectory = tmpDir;
-        this.debugLine = debugLine;
-        this.jdk = jdk;
-        this.workingDirectory = workingDirectory;
-        this.modelProperties = modelProperties;
-        this.argLine = argLine;
-        this.environmentVariables = toImmutable( environmentVariables );
-        this.debug = debugEnabled;
-        this.forkCount = forkCount;
-        this.reuseForks = reuseForks;
-        this.pluginPlatform = pluginPlatform;
-    }
-
-    public Classpath getBootClasspath()
-    {
-        return bootClasspathConfiguration;
-    }
-
-    public static String getEffectiveForkMode( String forkMode )
-    {
-        if ( "pertest".equalsIgnoreCase( forkMode ) )
-        {
-            return FORK_ALWAYS;
-        }
-        else if ( "none".equalsIgnoreCase( forkMode ) )
-        {
-            return FORK_NEVER;
-        }
-        else if ( forkMode.equals( FORK_NEVER ) || forkMode.equals( FORK_ONCE )
-               || forkMode.equals( FORK_ALWAYS ) || forkMode.equals( 
FORK_PERTHREAD ) )
-        {
-            return forkMode;
-        }
-        else
-        {
-            throw new IllegalArgumentException( "Fork mode " + forkMode + " is 
not a legal value" );
-        }
-    }
+    public static final String DEFAULT_PROVIDER_CLASS = 
ForkedBooter.class.getName();
+
+    @Nonnull public abstract File getTempDirectory();
+    @Nullable protected abstract String getDebugLine();
+    @Nonnull protected abstract File getWorkingDirectory();
+    @Nonnull protected abstract Properties getModelProperties();
+    @Nullable protected abstract String getArgLine();
+    @Nonnull protected abstract Map<String, String> getEnvironmentVariables();
+    protected abstract boolean isDebug();
+    protected abstract int getForkCount();
+    protected abstract boolean isReuseForks();
+    @Nonnull protected abstract Platform getPluginPlatform();
+    @Nonnull protected abstract JdkAttributes getJdkForTests();
+    @Nonnull protected abstract Classpath getBooterClasspath();
 
     /**
-     * @param classPath            cli the classpath arguments
      * @param config               The startup configuration
-     * @param threadNumber         the thread number, to be the replacement in 
the argLine   @return A commandline
+     * @param forkNumber           index of forked JVM, to be the replacement 
in the argLine
      * @return CommandLine able to flush entire command going to be sent to 
forked JVM
      * @throws org.apache.maven.surefire.booter.SurefireBooterForkException
      *          when unable to perform the fork
      */
-    public OutputStreamFlushableCommandline createCommandLine( List<String> 
classPath, StartupConfiguration config,
-                                                               int 
threadNumber )
-            throws SurefireBooterForkException
-    {
-        boolean useJar = 
config.getClassLoaderConfiguration().isManifestOnlyJarRequestedAndUsable();
-
-        boolean shadefire = config.isShadefire();
-
-        String providerThatHasMainMethod =
-                config.isProviderMainClass() ? config.getActualClassName() : 
ForkedBooter.class.getName();
-
-        return createCommandLine( classPath, useJar, shadefire, 
providerThatHasMainMethod, threadNumber );
-    }
-
-    OutputStreamFlushableCommandline createCommandLine( List<String> 
classPath, boolean useJar, boolean shadefire,
-                                                        String 
providerThatHasMainMethod, int threadNumber )
-        throws SurefireBooterForkException
-    {
-        OutputStreamFlushableCommandline cli = new 
OutputStreamFlushableCommandline();
-
-        cli.setExecutable( jdk.getJvmExecutable() );
-
-        String jvmArgLine =
-                replaceThreadNumberPlaceholder( stripNewLines( 
replacePropertyExpressions() ), threadNumber );
-
-        if ( jdk.isJava9AtLeast() && !jvmArgLine.contains( "--add-modules" ) )
-        {
-            if ( jvmArgLine.isEmpty() )
-            {
-                jvmArgLine = "--add-modules java.se.ee";
-            }
-            else
-            {
-                jvmArgLine = "--add-modules java.se.ee " + jvmArgLine;
-            }
-        }
-
-        if ( !jvmArgLine.isEmpty() )
-        {
-            cli.createArg().setLine( jvmArgLine );
-        }
-
-        for ( Map.Entry<String, String> entry : 
environmentVariables.entrySet() )
-        {
-            String value = entry.getValue();
-            cli.addEnvironment( entry.getKey(), value == null ? "" : value );
-        }
-
-        if ( getDebugLine() != null && !getDebugLine().isEmpty() )
-        {
-            cli.createArg().setLine( getDebugLine() );
-        }
-
-        if ( useJar )
-        {
-            try
-            {
-                File jarFile = createJar( classPath, providerThatHasMainMethod 
);
-                cli.createArg().setValue( "-jar" );
-                cli.createArg().setValue( escapeToPlatformPath( 
jarFile.getAbsolutePath() ) );
-            }
-            catch ( IOException e )
-            {
-                throw new SurefireBooterForkException( "Error creating archive 
file", e );
-            }
-        }
-        else
-        {
-            cli.addEnvironment( "CLASSPATH", join( classPath.iterator(), 
File.pathSeparator ) );
-
-            final String forkedBooter =
-                providerThatHasMainMethod != null ? providerThatHasMainMethod 
: ForkedBooter.class.getName();
-
-            cli.createArg().setValue( shadefire ? new Relocator().relocate( 
forkedBooter ) : forkedBooter );
-        }
-
-        cli.setWorkingDirectory( getWorkingDirectory( threadNumber 
).getAbsolutePath() );
-
-        return cli;
-    }
-
-    private File getWorkingDirectory( int threadNumber )
-        throws SurefireBooterForkException
-    {
-        File cwd = new File( replaceThreadNumberPlaceholder( 
workingDirectory.getAbsolutePath(), threadNumber ) );
-        if ( !cwd.exists() && !cwd.mkdirs() )
-        {
-            throw new SurefireBooterForkException( "Cannot create 
workingDirectory " + cwd.getAbsolutePath() );
-        }
-        if ( !cwd.isDirectory() )
-        {
-            throw new SurefireBooterForkException(
-                "WorkingDirectory " + cwd.getAbsolutePath() + " exists and is 
not a directory" );
-        }
-        return cwd;
-    }
-
-    private String replaceThreadNumberPlaceholder( String argLine, int 
threadNumber )
-    {
-        return argLine.replace( AbstractSurefireMojo.THREAD_NUMBER_PLACEHOLDER,
-                                String.valueOf( threadNumber ) ).replace( 
AbstractSurefireMojo.FORK_NUMBER_PLACEHOLDER,
-                                                                          
String.valueOf( threadNumber ) );
-    }
-
-    /**
-     * Replaces expressions <pre>@{property-name}</pre> with the corresponding 
properties
-     * from the model. This allows late evaluation of property values when the 
plugin is executed (as compared
-     * to evaluation when the pom is parsed as is done with 
<pre>${property-name}</pre> expressions).
-     *
-     * This allows other plugins to modify or set properties with the changes 
getting picked up by surefire.
-     */
-    private String replacePropertyExpressions()
-    {
-        if ( argLine == null )
-        {
-            return "";
-        }
-
-        String resolvedArgLine = argLine.trim();
-
-        if ( resolvedArgLine.isEmpty() )
-        {
-            return "";
-        }
-
-        for ( final String key : modelProperties.stringPropertyNames() )
-        {
-            String field = "@{" + key + "}";
-            if ( argLine.contains( field ) )
-            {
-                resolvedArgLine = resolvedArgLine.replace( field, 
modelProperties.getProperty( key, "" ) );
-            }
-        }
-
-        return resolvedArgLine;
-    }
-
-    /**
-     * Create a jar with just a manifest containing a Main-Class entry for 
BooterConfiguration and a Class-Path entry
-     * for all classpath elements.
-     *
-     * @param classPath      List&lt;String&gt; of all classpath elements.
-     * @param startClassName  The classname to start (main-class)
-     * @return The file pointint to the jar
-     * @throws java.io.IOException When a file operation fails.
-     */
-    private File createJar( List<String> classPath, String startClassName )
-        throws IOException
-    {
-        File file = File.createTempFile( "surefirebooter", ".jar", 
tempDirectory );
-        if ( !debug )
-        {
-            file.deleteOnExit();
-        }
-        FileOutputStream fos = new FileOutputStream( file );
-        JarOutputStream jos = new JarOutputStream( fos );
-        try
-        {
-            jos.setLevel( JarOutputStream.STORED );
-            JarEntry je = new JarEntry( "META-INF/MANIFEST.MF" );
-            jos.putNextEntry( je );
-
-            Manifest man = new Manifest();
-
-            // we can't use StringUtils.join here since we need to add a '/' to
-            // the end of directory entries - otherwise the jvm will ignore 
them.
-            StringBuilder cp = new StringBuilder();
-            for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
-            {
-                File file1 = new File( it.next() );
-                String uri = file1.toURI().toASCIIString();
-                cp.append( uri );
-                if ( file1.isDirectory() && !uri.endsWith( "/" ) )
-                {
-                    cp.append( '/' );
-                }
-
-                if ( it.hasNext() )
-                {
-                    cp.append( ' ' );
-                }
-            }
-
-            man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
-            man.getMainAttributes().putValue( "Class-Path", 
cp.toString().trim() );
-            man.getMainAttributes().putValue( "Main-Class", startClassName );
-
-            man.write( jos );
-
-            jos.closeEntry();
-            jos.flush();
-
-            return file;
-        }
-        finally
-        {
-            jos.close();
-        }
-    }
-
-    public boolean isDebug()
-    {
-        return debug;
-    }
-
-    public String getDebugLine()
-    {
-        return debugLine;
-    }
-
-    public File getTempDirectory()
-    {
-        return tempDirectory;
-    }
-
-    public int getForkCount()
-    {
-        return forkCount;
-    }
-
-    public boolean isReuseForks()
-    {
-        return reuseForks;
-    }
-
-    public Platform getPluginPlatform()
-    {
-        return pluginPlatform;
-    }
-
-    private static String stripNewLines( String argLine )
-    {
-        return argLine.replace( "\n", " " ).replace( "\r", " " );
-    }
-
-    /**
-     * Immutable map.
-     *
-     * @param map    immutable map copies elements from <code>map</code>
-     * @param <K>    key type
-     * @param <V>    value type
-     * @return never returns null
-     */
-    private static <K, V> Map<K, V> toImmutable( Map<K, V> map )
-    {
-        return map == null ? Collections.<K, V>emptyMap() : new 
ImmutableMap<K, V>( map );
-    }
+    @Nonnull
+    public abstract OutputStreamFlushableCommandline createCommandLine( 
@Nonnull StartupConfiguration config,
+                                                                        int 
forkNumber )
+            throws SurefireBooterForkException;
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/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 31237b2..a7c0311 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
@@ -35,8 +35,7 @@ import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
 import org.apache.maven.plugin.surefire.report.DefaultReporterFactory;
 import org.apache.maven.shared.utils.cli.CommandLineCallable;
 import org.apache.maven.shared.utils.cli.CommandLineException;
-import org.apache.maven.surefire.booter.Classpath;
-import org.apache.maven.surefire.booter.ClasspathConfiguration;
+import org.apache.maven.surefire.booter.AbstractPathConfiguration;
 import org.apache.maven.surefire.booter.KeyValueSource;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
@@ -88,7 +87,6 @@ import static 
org.apache.maven.plugin.surefire.booterclient.lazytestprovider.Tes
 import static 
org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLineAsCallable;
 import static 
org.apache.maven.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
 import static 
org.apache.maven.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
-import static org.apache.maven.surefire.booter.Classpath.join;
 import static 
org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile;
 import static org.apache.maven.surefire.suite.RunResult.SUCCESS;
 import static org.apache.maven.surefire.suite.RunResult.failure;
@@ -552,13 +550,11 @@ public class ForkStarter
         {
             tempDir = forkConfiguration.getTempDirectory().getCanonicalPath();
             BooterSerializer booterSerializer = new BooterSerializer( 
forkConfiguration );
-
+            Long pluginPid = 
forkConfiguration.getPluginPlatform().getPluginPid();
             surefireProperties = booterSerializer.serialize( 
providerProperties, providerConfiguration,
-                                                                   
startupConfiguration, testSet,
-                                                                   
readTestsFromInStream,
-                                                                   
forkConfiguration.getPluginPlatform().getPid() );
+                    startupConfiguration, testSet, readTestsFromInStream, 
pluginPid );
 
-            log.debug( "Determined Maven Process ID " + 
forkConfiguration.getPluginPlatform().getPid() );
+            log.debug( "Determined Maven Process ID " + pluginPid );
 
             if ( effectiveSystemProperties != null )
             {
@@ -579,20 +575,9 @@ public class ForkStarter
             throw new SurefireBooterForkException( "Error creating properties 
files for forking", e );
         }
 
-        // this could probably be simplified further
-        final Classpath bootClasspathConfiguration = 
startupConfiguration.isProviderMainClass()
-            ? 
startupConfiguration.getClasspathConfiguration().getProviderClasspath()
-            : forkConfiguration.getBootClasspath();
-
-        Classpath bootClasspath = join(
-            join( bootClasspathConfiguration, 
startupConfiguration.getClasspathConfiguration().getTestClasspath() ),
-            
startupConfiguration.getClasspathConfiguration().getProviderClasspath() );
 
-        log.debug( bootClasspath.getLogMessage( "boot" ) );
-        log.debug( bootClasspath.getCompactLogMessage( "boot(compact)" ) );
 
-        OutputStreamFlushableCommandline cli =
-            forkConfiguration.createCommandLine( bootClasspath.getClassPath(), 
startupConfiguration, forkNumber );
+        OutputStreamFlushableCommandline cli = 
forkConfiguration.createCommandLine( startupConfiguration, forkNumber );
 
         if ( testProvidingInputStream != null )
         {
@@ -701,7 +686,7 @@ public class ForkStarter
     {
         try
         {
-            final ClasspathConfiguration classpathConfiguration = 
startupConfiguration.getClasspathConfiguration();
+            AbstractPathConfiguration classpathConfiguration = 
startupConfiguration.getClasspathConfiguration();
             ClassLoader unifiedClassLoader = 
classpathConfiguration.createMergedClassLoader();
 
             CommonReflector commonReflector = new CommonReflector( 
unifiedClassLoader );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/b436a15e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
new file mode 100644
index 0000000..0c19cdf
--- /dev/null
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/JarManifestForkConfiguration.java
@@ -0,0 +1,139 @@
+package org.apache.maven.plugin.surefire.booterclient;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import 
org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline;
+import org.apache.maven.plugin.surefire.log.api.ConsoleLogger;
+import org.apache.maven.surefire.booter.Classpath;
+import org.apache.maven.surefire.booter.StartupConfiguration;
+import org.apache.maven.surefire.booter.SurefireBooterForkException;
+
+import javax.annotation.Nonnull;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+
+import static 
org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath;
+
+/**
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.21.0.Jigsaw
+ */
+public final class JarManifestForkConfiguration
+    extends AbstractClasspathForkConfiguration
+{
+    @SuppressWarnings( "checkstyle:parameternumber" )
+    public JarManifestForkConfiguration( Classpath bootClasspath, File 
tempDirectory, String debugLine,
+                                         File workingDirectory, Properties 
modelProperties, String argLine,
+                                         Map<String, String> 
environmentVariables, boolean debug, int forkCount,
+                                         boolean reuseForks, Platform 
pluginPlatform, ConsoleLogger log )
+    {
+        super( bootClasspath, tempDirectory, debugLine, workingDirectory, 
modelProperties, argLine,
+                environmentVariables, debug, forkCount, reuseForks, 
pluginPlatform, log );
+    }
+
+    @Override
+    protected void resolveClasspath( OutputStreamFlushableCommandline cli, 
String booterThatHasMainMethod,
+                                     StartupConfiguration config )
+            throws SurefireBooterForkException
+    {
+        try
+        {
+            File jar = createJar( toCompleteClasspath( config ), 
booterThatHasMainMethod );
+            cli.createArg().setValue( "-jar" );
+            cli.createArg().setValue( escapeToPlatformPath( 
jar.getAbsolutePath() ) );
+        }
+        catch ( IOException e )
+        {
+            throw new SurefireBooterForkException( "Error creating archive 
file", e );
+        }
+    }
+
+    /**
+     * Create a jar with just a manifest containing a Main-Class entry for 
BooterConfiguration and a Class-Path entry
+     * for all classpath elements.
+     *
+     * @param classPath      List&lt;String&gt; of all classpath elements.
+     * @param startClassName The class name to start (main-class)
+     * @return file of the jar
+     * @throws IOException When a file operation fails.
+     */
+    @Nonnull
+    private File createJar( @Nonnull List<String> classPath, @Nonnull String 
startClassName )
+            throws IOException
+    {
+        File file = File.createTempFile( "surefirebooter", ".jar", 
getTempDirectory() );
+        if ( !isDebug() )
+        {
+            file.deleteOnExit();
+        }
+        FileOutputStream fos = new FileOutputStream( file );
+        JarOutputStream jos = new JarOutputStream( fos );
+        try
+        {
+            jos.setLevel( JarOutputStream.STORED );
+            JarEntry je = new JarEntry( "META-INF/MANIFEST.MF" );
+            jos.putNextEntry( je );
+
+            Manifest man = new Manifest();
+
+            // we can't use StringUtils.join here since we need to add a '/' to
+            // the end of directory entries - otherwise the jvm will ignore 
them.
+            StringBuilder cp = new StringBuilder();
+            for ( Iterator<String> it = classPath.iterator(); it.hasNext(); )
+            {
+                File file1 = new File( it.next() );
+                String uri = file1.toURI().toASCIIString();
+                cp.append( uri );
+                if ( file1.isDirectory() && !uri.endsWith( "/" ) )
+                {
+                    cp.append( '/' );
+                }
+
+                if ( it.hasNext() )
+                {
+                    cp.append( ' ' );
+                }
+            }
+
+            man.getMainAttributes().putValue( "Manifest-Version", "1.0" );
+            man.getMainAttributes().putValue( "Class-Path", 
cp.toString().trim() );
+            man.getMainAttributes().putValue( "Main-Class", startClassName );
+
+            man.write( jos );
+
+            jos.closeEntry();
+            jos.flush();
+
+            return file;
+        }
+        finally
+        {
+            jos.close();
+        }
+    }
+}

Reply via email to