You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by kr...@apache.org on 2013/02/14 18:45:53 UTC

git commit: [SUREFIRE-956] make forkNumber unique among concurrent surefire-executions in a parallel maven build

Updated Branches:
  refs/heads/master 8b5f86cab -> 0cd869cce


[SUREFIRE-956] make forkNumber unique among concurrent surefire-executions in a parallel maven build


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

Branch: refs/heads/master
Commit: 0cd869ccec8dadb1c8b89baf8e8495819c236720
Parents: 8b5f86c
Author: Andreas Gudian <an...@gmail.com>
Authored: Tue Feb 5 22:01:03 2013 +0100
Committer: Kristian Rosenvold <kr...@apache.org>
Committed: Thu Feb 14 18:45:36 2013 +0100

----------------------------------------------------------------------
 .../plugin/surefire/AbstractSurefireMojo.java      |    4 +-
 .../surefire/booterclient/ForkNumberBucket.java    |   87 ++++++++
 .../plugin/surefire/booterclient/ForkStarter.java  |   57 ++----
 .../fork-options-and-parallel-execution.apt.vm     |   27 ++-
 .../org/apache/maven/surefire/its/ForkModeIT.java  |   24 +-
 .../maven/surefire/its/ForkModeMultiModuleIT.java  |  156 +++++++++++++++
 .../surefire/its/fixture/OutputValidator.java      |    6 +
 .../fork-mode-multimodule/module-a/pom.xml         |   36 ++++
 .../module-a/src/test/java/forkMode/Test1.java     |   48 +++++
 .../module-a/src/test/java/forkMode/Test2.java     |   17 ++
 .../module-a/src/test/java/forkMode/Test3.java     |   15 ++
 .../fork-mode-multimodule/module-b/pom.xml         |   36 ++++
 .../module-b/src/test/java/forkMode/Test1.java     |   48 +++++
 .../module-b/src/test/java/forkMode/Test2.java     |   17 ++
 .../module-b/src/test/java/forkMode/Test3.java     |   15 ++
 .../test/resources/fork-mode-multimodule/pom.xml   |   69 +++++++
 16 files changed, 608 insertions(+), 54 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/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 3b5af70..d97848d 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
@@ -451,7 +451,9 @@ public abstract class AbstractSurefireMojo
      * Example values: "1.5C", "4"<br/>
      * <br/>
      * The system properties and the <code>argLine</code> of the forked processes may contain the place holder string <code>${surefire.forkNumber}</code>,
-     * which is replaced with a fixed number for each of the parallel forks, ranging from <code>1</code> to the effective value of <code>forkCount</code>.
+     * which is replaced with a fixed number for each of the parallel forks, ranging from <code>1</code> to the effective value of <code>forkCount</code>
+     * times the maximum number of parallel Surefire executions in maven parallel builds, i.e. the effective value of the <code>-T</code> command line
+     * argument of maven core.
      * 
      * @since 2.14
      */

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkNumberBucket.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkNumberBucket.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkNumberBucket.java
new file mode 100644
index 0000000..c107982
--- /dev/null
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkNumberBucket.java
@@ -0,0 +1,87 @@
+package org.apache.maven.plugin.surefire.booterclient;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A bucket from which fork numbers can be drawn. Any drawn number needs to be returned to the bucket, in order to keep
+ * the range of provided values delivered as small as possible.
+ * 
+ * @author Andreas Gudian
+ */
+public class ForkNumberBucket
+{
+
+    private static final ForkNumberBucket INSTANCE = new ForkNumberBucket();
+
+    private Queue<Integer> qFree = new ConcurrentLinkedQueue<Integer>();
+
+    private AtomicInteger highWaterMark = new AtomicInteger( 1 );
+
+    /**
+     * Non-public constructor
+     */
+    protected ForkNumberBucket()
+    {
+    }
+
+    /**
+     * @return a fork number that is not currently in use. The value must be returned to the bucket using
+     *         {@link #returnNumber(int)}.
+     */
+    public static int drawNumber()
+    {
+        return getInstance()._drawNumber();
+    }
+
+    /**
+     * @param number the number to return to the bucket so that it can be reused.
+     */
+    public static void returnNumber( int number )
+    {
+        getInstance()._returnNumber( number );
+    }
+
+    /**
+     * @return a singleton instance
+     */
+    private static ForkNumberBucket getInstance()
+    {
+        return INSTANCE;
+    }
+
+    /**
+     * @return a fork number that is not currently in use. The value must be returned to the bucket using
+     *         {@link #returnNumber(int)}.
+     */
+    protected int _drawNumber()
+    {
+        Integer nextFree = qFree.poll();
+
+        if ( null == nextFree )
+        {
+            return highWaterMark.getAndIncrement();
+        }
+        else
+        {
+            return nextFree.intValue();
+        }
+    }
+
+    /**
+     * @return the highest number that has been drawn
+     */
+    protected int getHighestDrawnNumber()
+    {
+        return highWaterMark.get() - 1;
+    }
+
+    /**
+     * @param number the number to return to the bucket so that it can be reused.
+     */
+    protected void _returnNumber( int number )
+    {
+        qFree.add( Integer.valueOf( number ) );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/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 a8da513..8dd43da 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
@@ -36,7 +36,6 @@ import java.util.concurrent.Future;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.maven.plugin.surefire.AbstractSurefireMojo;
 import org.apache.maven.plugin.surefire.CommonReflector;
@@ -126,17 +125,6 @@ public class ForkStarter
 
     private static volatile int systemPropertiesFileCounter = 0;
 
-    private final ThreadLocal<Integer> forkNumber = new ThreadLocal<Integer>()
-    {
-        private final AtomicInteger nextforkNumber = new AtomicInteger( 1 );
-
-        @Override
-        protected Integer initialValue()
-        {
-            return nextforkNumber.getAndIncrement();
-        }
-    };
-
     public ForkStarter( ProviderConfiguration providerConfiguration, StartupConfiguration startupConfiguration,
                         ForkConfiguration forkConfiguration, int forkedProcessTimeoutInSeconds,
                         StartupReportConfiguration startupReportConfiguration )
@@ -162,13 +150,9 @@ public class ForkStarter
                 final ForkClient forkClient =
                     new ForkClient( defaultReporterFactory, startupReportConfiguration.getTestVmSystemProperties() );
                 result =
-                    fork( null, new PropertiesWrapper( providerProperties ), forkClient, effectiveSystemProperties, 1,
+                    fork( null, new PropertiesWrapper( providerProperties ), forkClient, effectiveSystemProperties,
                           null );
             }
-            else if ( isForkAlways() )
-            {
-                result = runSuitesForkPerTestSet( effectiveSystemProperties, 1 );
-            }
             else
             {
                 if ( forkConfiguration.isReuseForks() )
@@ -188,11 +172,6 @@ public class ForkStarter
         return result;
     }
 
-    private boolean isForkAlways()
-    {
-        return !forkConfiguration.isReuseForks() && 1 == forkConfiguration.getForkCount();
-    }
-
     private boolean isForkOnce()
     {
         return forkConfiguration.isReuseForks() && 1 == forkConfiguration.getForkCount();
@@ -226,8 +205,6 @@ public class ForkStarter
 
             for ( int forkNum = 0; forkNum < forkCount && forkNum < suites.size(); forkNum++ )
             {
-                final int finalForkNumber = forkNum + 1;
-
                 Callable<RunResult> pf = new Callable<RunResult>()
                 {
                     public RunResult call()
@@ -241,7 +218,7 @@ public class ForkStarter
                                             testProvidingInputStream );
 
                         return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
-                                     forkClient, effectiveSystemProperties, finalForkNumber, testProvidingInputStream );
+                                     forkClient, effectiveSystemProperties, testProvidingInputStream );
                     }
                 };
 
@@ -302,19 +279,11 @@ public class ForkStarter
                     public RunResult call()
                         throws Exception
                     {
-                        int thisThreadsForkNumber = forkNumber.get();
-                        if ( thisThreadsForkNumber > forkCount )
-                        {
-                            // this would be a bug in the ThreadPoolExecutor
-                            throw new IllegalStateException( "More threads than " + forkCount
-                                + " have been created by the ThreadPoolExecutor." );
-                        }
-
                         ForkClient forkClient =
                             new ForkClient( defaultReporterFactory,
                                             startupReportConfiguration.getTestVmSystemProperties() );
                         return fork( testSet, new PropertiesWrapper( providerConfiguration.getProviderProperties() ),
-                                     forkClient, effectiveSystemProperties, thisThreadsForkNumber, null );
+                                     forkClient, effectiveSystemProperties, null );
                     }
                 };
                 results.add( executorService.submit( pf ) );
@@ -370,6 +339,23 @@ public class ForkStarter
     }
 
     private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
+                            SurefireProperties effectiveSystemProperties,
+                            TestProvidingInputStream testProvidingInputStream )
+        throws SurefireBooterForkException
+    {
+        int forkNumber = ForkNumberBucket.drawNumber();
+        try
+        {
+            return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber,
+                         testProvidingInputStream );
+        }
+        finally
+        {
+            ForkNumberBucket.returnNumber( forkNumber );
+        }
+    }
+
+    private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient,
                             SurefireProperties effectiveSystemProperties, int forkNumber,
                             TestProvidingInputStream testProvidingInputStream )
         throws SurefireBooterForkException
@@ -387,8 +373,7 @@ public class ForkStarter
             if ( effectiveSystemProperties != null )
             {
                 SurefireProperties filteredProperties =
-                    AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties,
-                                                                                   forkNumber );
+                    AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, forkNumber );
                 systPropsFile =
                     SystemPropertyManager.writePropertiesFile( filteredProperties,
                                                                forkConfiguration.getTempDirectory(), "surefire_"

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
----------------------------------------------------------------------
diff --git a/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm b/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
index 444cbd1..5e47b0d 100644
--- a/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
@@ -62,6 +62,12 @@ Fork Options and Parallel Test Execution
   <<<forkCount>>> to a value higher than 1. The next section covers the details 
   about this and the related <<<reuseForks>>> property.
 
+* Parallel Surefire Execution in Multi-Module Maven Parallel Build
+
+  Maven core allows building modules of multi-module projects in parallel with
+  the command line option <<<-T>>>. This <multiplies> the extent of concurrency
+  configured directly in Surefire.
+
 * Forked Test Execution
 
   The parameter <<<forkCount>>> defines the maximum number of JVM processes
@@ -92,11 +98,14 @@ Fork Options and Parallel Test Execution
   specify variables and values to be added to the system properties during the
   test execution.
 
-  You can use the place holder <<<$\{surefire.forkNumber\}>>> within
-  <<<argLine>>>, or within the system properties (both those specified via
-  <<<mvn test -D...>>> and via <<<systemPropertyVariables>>>). Before executing
-  the tests, Surefire replaces that place holder by the number of the actually
-  executing process, counting from 1 to the effective value of <<<forkCount>>>.
+  You can use the place holder <<<$\{surefire.forkNumber\}>>> within 
+  <<<argLine>>>, or within the system properties (both those specified via 
+  <<<mvn test -D...>>> and via <<<systemPropertyVariables>>>). Before executing 
+  the tests, Surefire replaces that place holder by the number of the actually 
+  executing process, counting from 1 to the effective value of <<<forkCount>>> 
+  times the maximum number of parallel Surefire executions in maven parallel 
+  builds, i.e. the effective value of the <<<-T>>> command line argument of 
+  maven core.
 
   In case forkig is disabled (<<<forkCount=0>>>), the place holder will be
   replaced with <1>.
@@ -128,6 +137,10 @@ Fork Options and Parallel Test Execution
 </plugins>
 +---+
 
+  In case of a multi module project with tests in different modules, you could 
+  also use, say, <<<mvn -T 2 ...>>> to start the build, yielding values for 
+  <<<$\{surefire.forkNumber\}>>> ranging from 1 to 6.
+
   Imagine you execute some tests that use a JPA context, which has a notable
   initial startup time. By setting <<<reuseForks=true>>>, you can reuse that
   context for consecutive tests. And as many tests tend to use and access the
@@ -153,6 +166,10 @@ Fork Options and Parallel Test Execution
   processes, each of the processes can then use <<<threadCount>>> threads to
   execute the methods of one class in parallel.
 
+  Regarding the compatibility with multi-module parallel maven builds via 
+  <<<-T>>>, the only limitation is that you can not use it together with
+  <<<forkCount=0>>>.
+
 * Migrating the Deprecated forkMode Parameter to forkCount and reuseForks
 
   Surefire versions prior 2.14 used the parameter <<<forkMode>>> to configure

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java
index 5506caa..1077f4f 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeIT.java
@@ -38,7 +38,7 @@ public class ForkModeIT
 {
     public void testForkModeAlways()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkAlways() );
+        String[] pids = doTest( unpack( getProject() ).setForkJvm( true ).forkAlways() );
         assertDifferentPids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
@@ -46,7 +46,7 @@ public class ForkModeIT
 
     public void testForkModePerTest()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkPerTest() );
+        String[] pids = doTest( unpack( getProject() ).setForkJvm( true ).forkPerTest() );
         assertDifferentPids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
@@ -54,7 +54,7 @@ public class ForkModeIT
 
     public void testForkModeNever()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkNever() );
+        String[] pids = doTest( unpack( getProject() ).forkNever() );
         assertSamePids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertEquals( "my pid is equal to pid 1 of the test", getMyPID(), pids[0] );
@@ -62,7 +62,7 @@ public class ForkModeIT
 
     public void testForkModeNone()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkMode( "none" ) );
+        String[] pids = doTest( unpack( getProject() ).forkMode( "none" ) );
         assertSamePids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertEquals( "my pid is equal to pid 1 of the test", getMyPID(), pids[0] );
@@ -70,7 +70,7 @@ public class ForkModeIT
 
     public void testForkModeOncePerThreadSingleThread()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkOncePerThread().threadCount( 1 ) );
+        String[] pids = doTest( unpack( getProject() ).setForkJvm( true ).forkOncePerThread().threadCount( 1 ) );
         assertSamePids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
@@ -78,14 +78,14 @@ public class ForkModeIT
 
     public void testForkModeOncePerThreadTwoThreads()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkOncePerThread().threadCount( 2 ).addGoal( "-DsleepLength=1200" ) );
+        String[] pids = doTest( unpack( getProject() ).forkOncePerThread().threadCount( 2 ).addGoal( "-DsleepLength=1200" ) );
         assertDifferentPids( pids, 2 );
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
     }
 
     public void testForkCountZero()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkCount( 0 ) );
+        String[] pids = doTest( unpack( getProject() ).forkCount( 0 ) );
         assertSamePids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertEquals( "my pid is equal to pid 1 of the test", getMyPID(), pids[0] );
@@ -93,7 +93,7 @@ public class ForkModeIT
 
     public void testForkCountOneNoReuse()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkCount( 1 ).reuseForks( false ) );
+        String[] pids = doTest( unpack( getProject() ).setForkJvm( true ).forkCount( 1 ).reuseForks( false ) );
         assertDifferentPids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
@@ -101,7 +101,7 @@ public class ForkModeIT
 
     public void testForkCountOneReuse()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkCount( 1 ).reuseForks( true ) );
+        String[] pids = doTest( unpack( getProject() ).setForkJvm( true ).forkCount( 1 ).reuseForks( true ) );
         assertSamePids( pids );
         assertEndWith( pids, "_1_1", 3);
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
@@ -109,14 +109,14 @@ public class ForkModeIT
 
     public void testForkCountTwoNoReuse()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkCount( 2 ).reuseForks( false ).addGoal( "-DsleepLength=1200" ) );
+        String[] pids = doTest( unpack( getProject() ).forkCount( 2 ).reuseForks( false ).addGoal( "-DsleepLength=1200" ) );
         assertDifferentPids( pids );
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
     }
 
     public void testForkCountTwoReuse()
     {
-        String[] pids = doTest( unpack( getProject() ).debugLogging().forkCount( 2 ).reuseForks( true ).addGoal( "-DsleepLength=1200" ) );
+        String[] pids = doTest( unpack( getProject() ).forkCount( 2 ).reuseForks( true ).addGoal( "-DsleepLength=1200" ) );
         assertDifferentPids( pids, 2 );
         assertFalse( "pid 1 is not the same as the main process' pid", pids[0].equals( getMyPID() ) );
     }
@@ -178,7 +178,7 @@ public class ForkModeIT
     private String[] doTest( SurefireLauncher forkMode )
     {
         forkMode.sysProp( "testProperty", "testValue_${surefire.threadNumber}_${surefire.forkNumber}" );
-        final OutputValidator outputValidator = forkMode.executeTest();
+        final OutputValidator outputValidator = forkMode.debugLogging().executeTest();
         outputValidator.verifyErrorFreeLog().assertTestSuiteResults( 3, 0, 0, 0 );
         String[] pids = new String[3];
         for ( int i = 1; i <= pids.length; i++ )

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeMultiModuleIT.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeMultiModuleIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeMultiModuleIT.java
new file mode 100644
index 0000000..6d972ad
--- /dev/null
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/ForkModeMultiModuleIT.java
@@ -0,0 +1,156 @@
+package org.apache.maven.surefire.its;
+
+/*
+ * 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.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.maven.surefire.its.fixture.HelperAssertions;
+import org.apache.maven.surefire.its.fixture.OutputValidator;
+import org.apache.maven.surefire.its.fixture.SurefireIntegrationTestCase;
+import org.apache.maven.surefire.its.fixture.SurefireLauncher;
+import org.apache.maven.surefire.its.fixture.TestFile;
+
+/**
+ * Test forkMode in a multi module project with parallel maven builds
+ * 
+ * @author Andreas Gudian
+ */
+public class ForkModeMultiModuleIT
+    extends SurefireIntegrationTestCase
+{
+    public void testForkCountOneNoReuse()
+    {
+        List<String> pids = doTest( unpack( getProject() ).forkCount( 1 ).reuseForks( false ) );
+        assertAllDifferentPids( pids );
+        int matchesOne = countSuffixMatches( pids, "_1_1");
+        int matchesTwo = countSuffixMatches( pids, "_2_2" );
+        assertTrue( "At least one fork had forkNumber 1", matchesOne >= 1 );
+        assertTrue( "At least one fork had forkNumber 2", matchesTwo >= 1 );
+        assertEquals( "No other forkNumbers than 1 and 2 have been used", 6, matchesOne + matchesTwo);
+    }
+
+    public void testForkCountOneReuse()
+    {
+        List<String> pids = doTest( unpack( getProject() ).forkCount( 1 ).reuseForks( true ) );
+        assertDifferentPids( pids, 2 );
+        assertEndWith( pids, "_1_1", 3 );
+        assertEndWith( pids, "_2_2", 3 );
+    }
+
+    public void testForkCountTwoNoReuse()
+    {
+        List<String> pids = doTest( unpack( getProject() ).forkCount( 2 ).reuseForks( false ) );
+        assertAllDifferentPids( pids );
+        int matchesOne = countSuffixMatches( pids, "_1_1");
+        int matchesTwo = countSuffixMatches( pids, "_2_2" );
+        int matchesThree = countSuffixMatches( pids, "_3_3");
+        int matchesFour = countSuffixMatches( pids, "_4_4" );
+        assertTrue( "At least one fork had forkNumber 1", matchesOne >= 1 );
+        assertTrue( "At least one fork had forkNumber 2", matchesTwo >= 1 );
+        assertTrue( "At least one fork had forkNumber 3", matchesThree >= 1 );
+        assertTrue( "At least one fork had forkNumber 4", matchesFour >= 1 );
+        assertEquals( "No other forkNumbers than 1, 2, 3, or 4 have been used", 6, matchesOne + matchesTwo + matchesThree + matchesFour );
+    }
+
+    public void testForkCountTwoReuse()
+    {
+        List<String> pids =
+            doTest( unpack( getProject() ).forkCount( 2 ).reuseForks( true ) );
+        assertDifferentPids( pids, 4 );
+        
+        int matchesOne = countSuffixMatches( pids, "_1_1");
+        int matchesTwo = countSuffixMatches( pids, "_2_2" );
+        int matchesThree = countSuffixMatches( pids, "_3_3");
+        int matchesFour = countSuffixMatches( pids, "_4_4" );
+        assertTrue( "At least one fork had forkNumber 1", matchesOne >= 1 );
+        assertTrue( "At least one fork had forkNumber 2", matchesTwo >= 1 );
+        assertTrue( "At least one fork had forkNumber 3", matchesThree >= 1 );
+        assertTrue( "At least one fork had forkNumber 4", matchesFour >= 1 );
+        assertEquals( "No other forkNumbers than 1, 2, 3, or 4 have been used", 6, matchesOne + matchesTwo + matchesThree + matchesFour );
+    }
+
+    private void assertEndWith( List<String> pids, String suffix, int expectedMatches )
+    {
+        int matches = countSuffixMatches( pids, suffix );
+
+        assertEquals( "suffix " + suffix + " matched the correct number of pids", expectedMatches, matches );
+    }
+
+    private int countSuffixMatches( List<String> pids, String suffix )
+    {
+        int matches = 0;
+        for ( String pid : pids )
+        {
+            if ( pid.endsWith( suffix ) )
+            {
+                matches++;
+            }
+        }
+        return matches;
+    }
+
+    private void assertDifferentPids( List<String> pids, int numOfDifferentPids )
+    {
+        Set<String> pidSet = new HashSet<String>( pids );
+        assertEquals( "number of different pids is not as expected", numOfDifferentPids, pidSet.size() );
+    }
+
+    private void assertAllDifferentPids( List<String> pids )
+    {
+        assertDifferentPids( pids, pids.size() );
+    }
+
+    private List<String> doTest( SurefireLauncher forkMode )
+    {
+        forkMode.addGoal( "-T 2" );
+        forkMode.sysProp( "testProperty", "testValue_${surefire.threadNumber}_${surefire.forkNumber}" );
+        final OutputValidator outputValidator = forkMode.setForkJvm( true ).executeTest();
+        List<String> pids = new ArrayList<String>( 6 );
+        pids.addAll( validateModule( outputValidator, "module-a" ) );
+        pids.addAll( validateModule( outputValidator, "module-b" ) );
+
+        return pids;
+    }
+
+    private List<String> validateModule( OutputValidator outputValidator, String module )
+    {
+        HelperAssertions.assertTestSuiteResults( 3, 0, 0, 0, new File( outputValidator.getBaseDir(), module ) );
+
+        List<String> pids = new ArrayList<String>( 3 );
+        for ( int i = 1; i <= 3; i++ )
+        {
+            final TestFile targetFile = outputValidator.getTargetFile( module, "test" + i + "-pid" );
+            String pid = targetFile.slurpFile();
+            pids.add( pid );
+        }
+        
+        return pids;
+    }
+
+    protected String getProject()
+    {
+        return "fork-mode-multimodule";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java
index 7208ae3..99e9a18 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/OutputValidator.java
@@ -147,6 +147,12 @@ public class OutputValidator
         return this;
     }
 
+    public TestFile getTargetFile( String modulePath, String fileName )
+    {
+        File targetDir = getSubFile( modulePath + "/target" );
+        return new TestFile( new File( targetDir, fileName ), this );
+    }
+
     public TestFile getTargetFile( String fileName )
     {
         File targetDir = getSubFile( "target" );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/pom.xml b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/pom.xml
new file mode 100644
index 0000000..cbaf407
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.plugins.surefire</groupId>
+        <artifactId>fork-mode-multimodule</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>fork-mode-multimodule.module-a</artifactId>
+    <name>Test for forkMode Module A</name>
+
+</project>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test1.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test1.java b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test1.java
new file mode 100644
index 0000000..08ac31d
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test1.java
@@ -0,0 +1,48 @@
+package forkMode;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class Test1
+    extends TestCase
+{
+
+    private static final Random RANDOM = new Random();
+
+    public void test1()
+        throws IOException, InterruptedException
+    {
+        int sleepLength = Integer.valueOf( System.getProperty( "sleepLength", "1500" ));
+        Thread.sleep(sleepLength);
+        dumpPidFile( this );
+    }
+
+    public static void dumpPidFile( TestCase test )
+        throws IOException
+    {
+        String fileName = test.getName() + "-pid";
+        File target = new File( "target" ).getCanonicalFile();  // getCanonicalFile required for embedded mode
+        if ( !( target.exists() && target.isDirectory() ) )
+        {
+            target = new File( "." );
+        }
+        File pidFile = new File( target, fileName );
+        FileWriter fw = new FileWriter( pidFile );
+        // DGF little known trick... this is guaranteed to be unique to the PID
+        // In fact, it usually contains the pid and the local host name!
+        String pid = ManagementFactory.getRuntimeMXBean().getName();
+        fw.write( pid );
+        fw.write( " " );
+        fw.write( System.getProperty( "testProperty", String.valueOf( RANDOM.nextLong() ) ) );
+        fw.flush();
+        fw.close();
+        System.out.println( "Done Writing pid file" + pidFile.getAbsolutePath() );
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test2.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test2.java b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test2.java
new file mode 100644
index 0000000..ce30de9
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test2.java
@@ -0,0 +1,17 @@
+package forkMode;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+public class Test2
+    extends TestCase
+{
+
+    public void test2() throws IOException, InterruptedException {
+        int sleepLength = Integer.valueOf( System.getProperty( "sleepLength", "1500" ));
+        Thread.sleep(sleepLength);
+        Test1.dumpPidFile(this);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test3.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test3.java b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test3.java
new file mode 100644
index 0000000..deb9296
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-a/src/test/java/forkMode/Test3.java
@@ -0,0 +1,15 @@
+package forkMode;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+public class Test3
+    extends TestCase
+{
+
+    public void test3() throws IOException {
+        Test1.dumpPidFile(this);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/pom.xml b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/pom.xml
new file mode 100644
index 0000000..ba076ce
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/pom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+~ 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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.maven.plugins.surefire</groupId>
+        <artifactId>fork-mode-multimodule</artifactId>
+        <version>1.0-SNAPSHOT</version>
+    </parent>
+
+    <groupId>org.apache.maven.plugins.surefire</groupId>
+    <artifactId>fork-mode-multimodule.module-b</artifactId>
+    <name>Test for forkMode Module B</name>
+
+</project>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test1.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test1.java b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test1.java
new file mode 100644
index 0000000..08ac31d
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test1.java
@@ -0,0 +1,48 @@
+package forkMode;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class Test1
+    extends TestCase
+{
+
+    private static final Random RANDOM = new Random();
+
+    public void test1()
+        throws IOException, InterruptedException
+    {
+        int sleepLength = Integer.valueOf( System.getProperty( "sleepLength", "1500" ));
+        Thread.sleep(sleepLength);
+        dumpPidFile( this );
+    }
+
+    public static void dumpPidFile( TestCase test )
+        throws IOException
+    {
+        String fileName = test.getName() + "-pid";
+        File target = new File( "target" ).getCanonicalFile();  // getCanonicalFile required for embedded mode
+        if ( !( target.exists() && target.isDirectory() ) )
+        {
+            target = new File( "." );
+        }
+        File pidFile = new File( target, fileName );
+        FileWriter fw = new FileWriter( pidFile );
+        // DGF little known trick... this is guaranteed to be unique to the PID
+        // In fact, it usually contains the pid and the local host name!
+        String pid = ManagementFactory.getRuntimeMXBean().getName();
+        fw.write( pid );
+        fw.write( " " );
+        fw.write( System.getProperty( "testProperty", String.valueOf( RANDOM.nextLong() ) ) );
+        fw.flush();
+        fw.close();
+        System.out.println( "Done Writing pid file" + pidFile.getAbsolutePath() );
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test2.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test2.java b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test2.java
new file mode 100644
index 0000000..ce30de9
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test2.java
@@ -0,0 +1,17 @@
+package forkMode;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+public class Test2
+    extends TestCase
+{
+
+    public void test2() throws IOException, InterruptedException {
+        int sleepLength = Integer.valueOf( System.getProperty( "sleepLength", "1500" ));
+        Thread.sleep(sleepLength);
+        Test1.dumpPidFile(this);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test3.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test3.java b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test3.java
new file mode 100644
index 0000000..deb9296
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/module-b/src/test/java/forkMode/Test3.java
@@ -0,0 +1,15 @@
+package forkMode;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+public class Test3
+    extends TestCase
+{
+
+    public void test3() throws IOException {
+        Test1.dumpPidFile(this);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/0cd869cc/surefire-integration-tests/src/test/resources/fork-mode-multimodule/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/fork-mode-multimodule/pom.xml b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/pom.xml
new file mode 100644
index 0000000..98e073f
--- /dev/null
+++ b/surefire-integration-tests/src/test/resources/fork-mode-multimodule/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ 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.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.plugins.surefire</groupId>
+  <artifactId>fork-mode-multimodule</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <name>Test for forkMode Multimodule</name>
+
+  <packaging>pom</packaging>
+
+  <modules>
+    <module>module-a</module>
+    <module>module-b</module>
+  </modules>
+  
+  <build>
+    <plugins>
+      <plugin>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <version>2.3.2</version>
+        <configuration>
+          <source>1.5</source>
+          <target>1.5</target>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-surefire-plugin</artifactId>
+        <version>${surefire.version}</version>
+        <configuration>
+          <forkMode>${forkMode}</forkMode>
+          <threadCount>${threadCount}</threadCount>
+          <runOrder>alphabetical</runOrder>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>3.8.1</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>