You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by ag...@apache.org on 2013/08/09 21:20:57 UTC

[1/3] [SUREFIRE-1021] New Parallel Computer for JUnit 4.7+ Submitted by: Tibor17

Updated Branches:
  refs/heads/master e224e8b49 -> 49c4a6259


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/ParallelComputerFactoryTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/ParallelComputerFactoryTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/ParallelComputerFactoryTest.java
new file mode 100644
index 0000000..90d773e
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/ParallelComputerFactoryTest.java
@@ -0,0 +1,907 @@
+package org.apache.maven.surefire.junitcore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.testset.TestSetFailedException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.theories.DataPoint;
+import org.junit.experimental.theories.Theories;
+import org.junit.experimental.theories.Theory;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+
+import java.util.Properties;
+
+import static org.apache.maven.surefire.junitcore.ParallelComputerFactory.*;
+import static org.apache.maven.surefire.junitcore.JUnitCoreParameters.*;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * Testing an algorithm in {@link ParallelComputerFactory} which configures
+ * allocated thread resources in ParallelComputer by given {@link JUnitCoreParameters}.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see org.apache.maven.surefire.junitcore.ParallelComputerFactory
+ */
+@RunWith(Theories.class)
+public final class ParallelComputerFactoryTest
+{
+    @Rule
+    public final ExpectedException exception = ExpectedException.none();
+
+    @DataPoint
+    public static final int CPU_1 = 1;
+
+    @DataPoint
+    public static final int CPU_4 = 4;
+
+    @BeforeClass
+    public static void beforeClass()
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( 1 );
+    }
+
+    @AfterClass
+    public static void afterClass()
+    {
+        ParallelComputerFactory.setDefaultAvailableProcessors();
+    }
+
+    @Test
+    public void unknownParallel() throws TestSetFailedException
+    {
+        Properties properties = new Properties();
+        exception.expect( TestSetFailedException.class  );
+        resolveConcurrency( new JUnitCoreParameters( properties ) );
+    }
+
+    @Test
+    public void unknownThreadCountSuites() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "suites" ) );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Test
+    public void unknownThreadCountClasses() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "classes" ) );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Test
+    public void unknownThreadCountMethods() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "methods" ) );
+        assertFalse( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Test
+    public void unknownThreadCountBoth() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "both" ) );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Test
+    public void unknownThreadCountAll() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "all" ) );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Test
+    public void unknownThreadCountSuitesAndClasses() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "suitesAndClasses" ) );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Test
+    public void unknownThreadCountSuitesAndMethods() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "suitesAndMethods" ) );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Test
+    public void unknownThreadCountClassesAndMethods() throws TestSetFailedException
+    {
+        JUnitCoreParameters params = new JUnitCoreParameters( parallel( "classesAndMethods" ) );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        exception.expect( TestSetFailedException.class );
+        resolveConcurrency( params );
+    }
+
+    @Theory
+    public void useUnlimitedThreadsSuites( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suites" );
+        properties.setProperty( USEUNLIMITEDTHREADS_KEY, "true" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 0 ) );
+
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void useUnlimitedThreadsClasses( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classes" );
+        properties.setProperty( USEUNLIMITEDTHREADS_KEY, "true" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.methods, is( 0 ) );
+
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "5" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 5 * cpu ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void unlimitedThreadsMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "methods" );
+        properties.setProperty( USEUNLIMITEDTHREADS_KEY, "true" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "5" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 5 * cpu ) );
+    }
+
+    @Theory
+    public void unlimitedThreadsSuitesAndClasses( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndClasses" );
+        properties.setProperty( USEUNLIMITEDTHREADS_KEY, "true" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.classes, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.methods, is( 0 ) );
+
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "15" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 15 * cpu ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void unlimitedThreadsSuitesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndMethods" );
+        properties.setProperty( USEUNLIMITEDTHREADS_KEY, "true" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "15" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 15 * cpu ) );
+    }
+
+    @Theory
+    public void unlimitedThreadsClassesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classesAndMethods" );
+        properties.setProperty( USEUNLIMITEDTHREADS_KEY, "true" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "5" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "15" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 5 * cpu ) );
+        assertThat( concurrency.methods, is( 15 * cpu ) );
+    }
+
+    @Theory
+    public void unlimitedThreadsAll( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "all" );
+        properties.setProperty( USEUNLIMITEDTHREADS_KEY, "true" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.classes, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "15" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "30" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 15 * cpu ) );
+        assertThat( concurrency.methods, is( 30 * cpu ) );
+    }
+
+    @Theory
+    public void threadCountSuites( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suites" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 0 ) );
+        assertThat( concurrency.suites, is( 3 * cpu ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void threadCountClasses( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classes" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 0 ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 3 * cpu ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void threadCountMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "methods" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 0 ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 3 * cpu ) );
+    }
+
+    @Theory
+    public void threadCountBoth( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "both" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( (int) ( ( 3d / 2 ) * cpu ) ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void threadCountClassesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classesAndMethods" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( (int) ( ( 3d / 2 ) * cpu ) ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void threadCountSuitesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndMethods" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        assertThat( concurrency.suites, is( (int) ( ( 3d / 2 ) * cpu ) ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void threadCountSuitesAndClasses( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndClasses" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        assertThat( concurrency.suites, is( (int) ( ( 3d / 2 ) * cpu ) ) );
+        assertThat( concurrency.classes, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void threadCountAll( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "all" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        assertThat( concurrency.suites, is( cpu ) );
+        assertThat( concurrency.classes, is( cpu ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void everyThreadCountSuitesAndClasses( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndClasses" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        // % percentage ratio
+        properties.setProperty( THREADCOUNTSUITES_KEY, "34" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "66" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is(3 * cpu) );
+        int concurrentSuites = (int) ( 0.34d * concurrency.capacity );
+        assertThat( concurrency.suites, is( concurrentSuites ) );
+        assertThat( concurrency.classes, is( concurrency.capacity - concurrentSuites ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void everyThreadCountSuitesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndMethods" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        // % percentage ratio
+        properties.setProperty( THREADCOUNTSUITES_KEY, "34" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "66" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        int concurrentSuites = (int) ( 0.34d * concurrency.capacity );
+        assertThat( concurrency.suites, is( concurrentSuites ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( concurrency.capacity - concurrentSuites ) );
+    }
+
+    @Theory
+    public void everyThreadCountClassesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classesAndMethods" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        // % percentage ratio
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "34" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "66" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        int concurrentClasses = (int) ( 0.34d * concurrency.capacity );
+        assertThat( concurrency.classes, is( concurrentClasses ) );
+        assertThat( concurrency.methods, is( concurrency.capacity - concurrentClasses ) );
+    }
+
+    @Theory
+    public void everyThreadCountAll( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "all" );
+        properties.setProperty( THREADCOUNT_KEY, "3" );
+        // % percentage ratio
+        properties.setProperty( THREADCOUNTSUITES_KEY, "17" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "34" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "49" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 3 * cpu ) );
+        int concurrentSuites = (int) ( 0.17d * concurrency.capacity );
+        int concurrentClasses = (int) ( 0.34d * concurrency.capacity );
+        assertThat( concurrency.suites, is( concurrentSuites ) );
+        assertThat( concurrency.classes, is( concurrentClasses ) );
+        assertThat( concurrency.methods, is( concurrency.capacity - concurrentSuites - concurrentClasses ) );
+    }
+
+    @Theory
+    public void reusableThreadCountSuitesAndClasses( int cpu ) throws TestSetFailedException
+    {
+        // 4 * cpu to 5 * cpu threads to run test classes
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndClasses" );
+        properties.setProperty( THREADCOUNT_KEY, "6" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "2" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 6 * cpu ) );
+        assertThat( concurrency.suites, is( 2 * cpu ) );
+        assertThat( concurrency.classes, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void reusableThreadCountSuitesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        // 4 * cpu to 5 * cpu threads to run test methods
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndMethods" );
+        properties.setProperty( THREADCOUNT_KEY, "6" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "2" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 6 * cpu ) );
+        assertThat( concurrency.suites, is( 2 * cpu ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void reusableThreadCountClassesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        // 4 * cpu to 5 * cpu threads to run test methods
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classesAndMethods" );
+        properties.setProperty( THREADCOUNT_KEY, "6" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "2" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 6 * cpu ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 2 * cpu ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void reusableThreadCountAll( int cpu ) throws TestSetFailedException
+    {
+        // 8 * cpu to 13 * cpu threads to run test methods
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "all" );
+        properties.setProperty( THREADCOUNT_KEY, "14" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "2" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "4" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 14 * cpu ) );
+        assertThat( concurrency.suites, is( 2 * cpu ) );
+        assertThat( concurrency.classes, is( 4 * cpu ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void suites( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suites" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 5 * cpu ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void classes( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classes" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "5" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 5 * cpu ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 5 * cpu ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void methods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "methods" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "5" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 5 * cpu ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 5 * cpu ) );
+    }
+
+    @Theory
+    public void suitesAndClasses( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+
+        properties.setProperty( PARALLEL_KEY, "suitesAndClasses" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "15" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 20 * cpu ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 15 * cpu ) );
+        assertThat( concurrency.methods, is( 0 ) );
+
+        // Warning: this case works but is not enabled in AbstractSurefireMojo
+        // Instead use the 'useUnlimitedThreads' parameter.
+        properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndClasses" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertFalse( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.methods, is( 0 ) );
+    }
+
+    @Theory
+    public void suitesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+
+        properties.setProperty( PARALLEL_KEY, "suitesAndMethods" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "15" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 20 * cpu ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( 15 * cpu ) );
+
+        // Warning: this case works but is not enabled in AbstractSurefireMojo
+        // Instead use the 'useUnlimitedThreads' parameter.
+        properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "suitesAndMethods" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertFalse( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 0 ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void classesAndMethods( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+
+        properties.setProperty( PARALLEL_KEY, "classesAndMethods" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "5" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "15" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 20 * cpu ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 5 * cpu ) );
+        assertThat( concurrency.methods, is( 15 * cpu ) );
+
+        // Warning: this case works but is not enabled in AbstractSurefireMojo
+        // Instead use the 'useUnlimitedThreads' parameter.
+        properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "classesAndMethods" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "5" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertFalse( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 0 ) );
+        assertThat( concurrency.classes, is( 5 * cpu ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    @Theory
+    public void all( int cpu ) throws TestSetFailedException
+    {
+        ParallelComputerFactory.overrideAvailableProcessors( cpu );
+        Properties properties = new Properties();
+
+        properties.setProperty( PARALLEL_KEY, "all" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "15" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "30" );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        Concurrency concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( 50 * cpu ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 15 * cpu ) );
+        assertThat( concurrency.methods, is( 30 * cpu ) );
+
+        // Warning: these cases work but they are not enabled in AbstractSurefireMojo
+        // Instead use the 'useUnlimitedThreads' parameter.
+        properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "all" );
+        properties.setProperty( THREADCOUNTSUITES_KEY, "5" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "15" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( 5 * cpu ) );
+        assertThat( concurrency.classes, is( 15 * cpu ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+
+        properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "all" );
+        properties.setProperty( THREADCOUNTCLASSES_KEY, "15" );
+        params = new JUnitCoreParameters( properties );
+        concurrency = resolveConcurrency( params );
+        assertTrue( params.isParallelSuites() );
+        assertTrue( params.isParallelClasses() );
+        assertTrue( params.isParallelMethod() );
+        assertThat( concurrency.capacity, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.suites, is( Integer.MAX_VALUE ) );
+        assertThat( concurrency.classes, is( 15 * cpu ) );
+        assertThat( concurrency.methods, is( Integer.MAX_VALUE ) );
+    }
+
+    private static Properties parallel( String parallel )
+    {
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, parallel );
+        return properties;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
index 190a280..ab312af 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/Surefire746Test.java
@@ -33,10 +33,13 @@ import org.apache.maven.surefire.report.ReporterConfiguration;
 import org.apache.maven.surefire.report.ReporterFactory;
 import org.apache.maven.surefire.report.RunListener;
 import org.apache.maven.surefire.suite.RunResult;
+import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.apache.maven.surefire.util.TestsToRun;
 
 import junit.framework.Assert;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 import org.junit.runner.notification.RunNotifier;
@@ -77,6 +80,8 @@ import org.junit.runners.model.InitializationError;
  */
 public class Surefire746Test
 {
+    @Rule
+    public final ExpectedException exception = ExpectedException.none();
 
     @Test
     public void surefireIsConfused_ByMultipleIgnore_OnClassLevel()
@@ -105,13 +110,19 @@ public class Surefire746Test
             new ArrayList<org.junit.runner.notification.RunListener>();
         customRunListeners.add( 0, jUnit4RunListener );
 
-        JUnitCoreWrapper.execute( testsToRun, jUnitCoreParameters, customRunListeners, null );
-
-        RunResult result = reporterFactory.close();
-
-        Assert.assertEquals( "JUnit should report correctly number of test ran(Finished)", 1,
-                             result.getCompletedCount() );
-
+        try
+        {
+            // JUnitCoreWrapper#execute() is calling JUnit4RunListener#rethrowAnyTestMechanismFailures()
+            // and rethrows a failure which happened in listener
+            exception.expect( TestSetFailedException.class );
+            JUnitCoreWrapper.execute( testsToRun, jUnitCoreParameters, customRunListeners, null );
+        }
+        finally
+        {
+            RunResult result = reporterFactory.close();
+            Assert.assertEquals( "JUnit should report correctly number of test ran(Finished)",
+                    1, result.getCompletedCount() );
+        }
     }
 
     @RunWith( TestCaseRunner.class )

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java
new file mode 100644
index 0000000..c34056a
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilderTest.java
@@ -0,0 +1,436 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.JUnitCore;
+import org.junit.runner.Result;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.hamcrest.core.AnyOf.anyOf;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.apache.maven.surefire.junitcore.pc.RangeMatcher.between;
+
+/**
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+public class ParallelComputerBuilderTest {
+    @Rule
+    public final Stopwatch runtime = new Stopwatch();
+
+    @Before
+    public void beforeTest() {
+        Class1.maxConcurrentMethods = 0;
+        Class1.concurrentMethods = 0;
+        shutdownTask = null;
+    }
+
+    @Test
+    public void parallelMethodsReuseOneOrTwoThreads() {
+        ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
+        parallelComputerBuilder.useOnePool(4);
+
+        // One thread because one suite: TestSuite, however the capacity is 5.
+        parallelComputerBuilder.parallelSuites(5);
+
+        // Two threads because TestSuite has two classes, however the capacity is 5.
+        parallelComputerBuilder.parallelClasses(5);
+
+        // One or two threads because one threads comes from '#useOnePool(4)'
+        // and next thread may be reused from finished class, however the capacity is 3.
+        parallelComputerBuilder.parallelMethods(3);
+
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
+        Result result = new JUnitCore().run(computer, TestSuite.class);
+        long timeSpent = runtime.stop();
+
+        assertThat(computer.suites.size(), is(1));
+        assertThat(computer.classes.size(), is(0));
+        assertThat(computer.nestedClasses.size(), is(2));
+        assertThat(computer.nestedSuites.size(), is(0));
+        assertFalse(computer.splitPool);
+        assertThat(computer.poolCapacity, is(4));
+        assertTrue(result.wasSuccessful());
+        if (Class1.maxConcurrentMethods == 1) {
+            assertThat(timeSpent, between(1950, 2250));
+        } else if (Class1.maxConcurrentMethods == 2) {
+            assertThat(timeSpent, between(1450, 1750));
+        } else {
+            fail();
+        }
+    }
+
+    @Test
+    public void suiteAndClassInOnePool() {
+        ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
+        parallelComputerBuilder.useOnePool(5);
+        parallelComputerBuilder.parallelSuites(5);
+        parallelComputerBuilder.parallelClasses(5);
+        parallelComputerBuilder.parallelMethods(3);
+
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
+        Result result = new JUnitCore().run(computer, TestSuite.class, Class1.class);
+        long timeSpent = runtime.stop();
+
+        assertThat(computer.suites.size(), is(1));
+        assertThat(computer.classes.size(), is(1));
+        assertThat(computer.nestedClasses.size(), is(2));
+        assertThat(computer.nestedSuites.size(), is(0));
+        assertFalse(computer.splitPool);
+        assertThat(computer.poolCapacity, is(5));
+        assertTrue(result.wasSuccessful());
+        assertThat(Class1.maxConcurrentMethods, is(2));
+        assertThat(timeSpent, anyOf(between(1450, 1750), between(1950, 2250), between(2450, 2750)));
+    }
+
+    @Test
+    public void onePoolWithUnlimitedParallelMethods() {
+        // see ParallelComputerBuilder Javadoc
+        ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
+        parallelComputerBuilder.useOnePool(8);
+        parallelComputerBuilder.parallelSuites(2);
+        parallelComputerBuilder.parallelClasses(4);
+        parallelComputerBuilder.parallelMethods();
+
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
+        Result result = new JUnitCore().run(computer, TestSuite.class, Class1.class);
+        long timeSpent = runtime.stop();
+
+        assertThat(computer.suites.size(), is(1));
+        assertThat(computer.classes.size(), is(1));
+        assertThat(computer.nestedClasses.size(), is(2));
+        assertThat(computer.nestedSuites.size(), is(0));
+        assertFalse(computer.splitPool);
+        assertThat(computer.poolCapacity, is(8));
+        assertTrue(result.wasSuccessful());
+        assertThat(Class1.maxConcurrentMethods, is(4));
+        assertThat(timeSpent, between(950, 1250));
+    }
+
+    @Test
+    public void underflowParallelism()
+    {
+        ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
+        parallelComputerBuilder.useOnePool( 3 );
+
+        // One thread because one suite: TestSuite.
+        parallelComputerBuilder.parallelSuites( 5 );
+
+        // One thread because of the limitation which is bottleneck.
+        parallelComputerBuilder.parallelClasses( 1 );
+
+        // One thread remains from '#useOnePool(3)'.
+        parallelComputerBuilder.parallelMethods( 3 );
+
+        ParallelComputerBuilder.PC computer = ( ParallelComputerBuilder.PC ) parallelComputerBuilder.buildComputer();
+        Result result = new JUnitCore().run( computer, TestSuite.class );
+        long timeSpent = runtime.stop();
+
+        assertThat( computer.suites.size(), is( 1 ) );
+        assertThat( computer.classes.size(), is( 0 ) );
+        assertThat( computer.nestedClasses.size(), is( 2 ) );
+        assertThat( computer.nestedSuites.size(), is( 0 ) );
+        assertFalse( computer.splitPool );
+        assertThat( computer.poolCapacity, is( 3 ) );
+        assertTrue( result.wasSuccessful() );
+        assertThat( Class1.maxConcurrentMethods, is( 1 ) );
+        assertThat( timeSpent, between( 1950, 2250 ) );
+    }
+
+    @Test
+    public void separatePoolsWithSuite() {
+        ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
+        parallelComputerBuilder.parallelSuites(5);
+        parallelComputerBuilder.parallelClasses(5);
+        parallelComputerBuilder.parallelMethods(3);
+
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
+        Result result = new JUnitCore().run(computer, TestSuite.class);
+        long timeSpent = runtime.stop();
+
+        assertThat(computer.suites.size(), is(1));
+        assertThat(computer.classes.size(), is(0));
+        assertThat(computer.nestedClasses.size(), is(2));
+        assertThat(computer.nestedSuites.size(), is(0));
+        assertTrue(computer.splitPool);
+        assertThat(computer.poolCapacity, is(ParallelComputerBuilder.TOTAL_POOL_SIZE_UNDEFINED));
+        assertTrue(result.wasSuccessful());
+        assertThat(Class1.maxConcurrentMethods, is(3));
+        assertThat(timeSpent, between(950, 1250));
+    }
+
+    @Test
+    public void separatePoolsWithSuiteAndClass() {
+        ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
+        parallelComputerBuilder.parallelSuites(5);
+        parallelComputerBuilder.parallelClasses(5);
+        parallelComputerBuilder.parallelMethods(3);
+
+        // 6 methods altogether.
+        // 2 groups with 3 threads.
+        // Each group takes 0.5s.
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
+        Result result = new JUnitCore().run(computer, TestSuite.class, Class1.class);
+        long timeSpent = runtime.stop();
+
+        assertThat(computer.suites.size(), is(1));
+        assertThat(computer.classes.size(), is(1));
+        assertThat(computer.nestedClasses.size(), is(2));
+        assertThat(computer.nestedSuites.size(), is(0));
+        assertTrue(computer.splitPool);
+        assertThat(computer.poolCapacity, is(ParallelComputerBuilder.TOTAL_POOL_SIZE_UNDEFINED));
+        assertTrue(result.wasSuccessful());
+        assertThat(Class1.maxConcurrentMethods, is(3));
+        assertThat(timeSpent, between(950, 1250));
+    }
+
+    @Test
+    public void separatePoolsWithSuiteAndSequentialClasses() {
+        ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder();
+        parallelComputerBuilder.parallelSuites(5);
+        parallelComputerBuilder.parallelClasses(1);
+        parallelComputerBuilder.parallelMethods(3);
+
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
+        Result result = new JUnitCore().run(computer, TestSuite.class, Class1.class);
+        long timeSpent = runtime.stop();
+
+        assertThat(computer.suites.size(), is(1));
+        assertThat(computer.classes.size(), is(1));
+        assertThat(computer.nestedClasses.size(), is(2));
+        assertThat(computer.nestedSuites.size(), is(0));
+        assertTrue(computer.splitPool);
+        assertThat(computer.poolCapacity, is(ParallelComputerBuilder.TOTAL_POOL_SIZE_UNDEFINED));
+        assertTrue(result.wasSuccessful());
+        assertThat(Class1.maxConcurrentMethods, is(2));
+        assertThat(timeSpent, between(1450, 1750));
+    }
+
+    private static class ShutdownTest {
+        Result run(final boolean useInterrupt) {
+            ParallelComputerBuilder parallelComputerBuilder = new ParallelComputerBuilder().useOnePool(8);
+            parallelComputerBuilder.parallelSuites(2);
+            parallelComputerBuilder.parallelClasses(3);
+            parallelComputerBuilder.parallelMethods(3);
+
+            final ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) parallelComputerBuilder.buildComputer();
+            shutdownTask = new Runnable() {
+                public void run() {
+                    Collection<org.junit.runner.Description> startedTests = computer.shutdown(useInterrupt);
+                    assertThat(startedTests.size(), is(not(0)));
+                }
+            };
+            return new JUnitCore().run(computer, TestSuite.class, Class2.class, Class3.class);
+        }
+    }
+
+    @Test(timeout = 2000)
+    public void shutdown() {
+        Result result = new ShutdownTest().run(false);
+        long timeSpent = runtime.stop();
+        assertTrue(result.wasSuccessful());
+        assertTrue(beforeShutdown);
+        assertThat(timeSpent, between(450, 1250));
+    }
+
+    @Test(timeout = 2000)
+    public void shutdownWithInterrupt() {
+        new ShutdownTest().run(true);
+        long timeSpent = runtime.stop();
+        assertTrue(beforeShutdown);
+        assertThat(timeSpent, between(450, 1250));
+    }
+
+    @Test
+    public void nothingParallel() {
+        JUnitCore core = new JUnitCore();
+        ParallelComputerBuilder builder = new ParallelComputerBuilder();
+
+        Result result = core.run(builder.buildComputer(), NothingDoingTest1.class, NothingDoingTest2.class);
+        assertTrue(result.wasSuccessful());
+
+        result = core.run(builder.buildComputer(), NothingDoingTest1.class, NothingDoingSuite.class);
+        assertTrue(result.wasSuccessful());
+
+        result = core.run(builder.useOnePool(1).buildComputer(), NothingDoingTest1.class, NothingDoingTest2.class);
+        assertTrue(result.wasSuccessful());
+
+        result = core.run(builder.useOnePool(1).buildComputer(), NothingDoingTest1.class, NothingDoingSuite.class);
+        assertTrue(result.wasSuccessful());
+
+        result = core.run(builder.useOnePool(2).buildComputer(), NothingDoingTest1.class, NothingDoingSuite.class);
+        assertTrue(result.wasSuccessful());
+
+        Class<?>[] classes = {NothingDoingTest1.class, NothingDoingSuite.class};
+
+        result = core.run(builder.useOnePool(2).parallelSuites(1).parallelClasses(1).buildComputer(), classes);
+        assertTrue(result.wasSuccessful());
+
+        result = core.run(builder.useOnePool(2).parallelSuites(1).parallelClasses().buildComputer(), classes);
+        assertTrue(result.wasSuccessful());
+
+        classes = new Class<?>[]{NothingDoingSuite.class, NothingDoingSuite.class,
+                NothingDoingTest1.class, NothingDoingTest2.class, NothingDoingTest3.class};
+
+        result = core.run(builder.useOnePool(2).parallelSuites(1).parallelClasses(1).buildComputer(), classes);
+        assertTrue(result.wasSuccessful());
+
+        result = core.run(builder.useOnePool(2).parallelSuites(1).parallelClasses().buildComputer(), classes);
+        assertTrue(result.wasSuccessful());
+    }
+
+    private static void testKeepBeforeAfter(ParallelComputerBuilder builder, Class<?>... classes) {
+        JUnitCore core = new JUnitCore();
+        for (int round = 0; round < 5; round++) {
+            NothingDoingTest1.methods.clear();
+            Result result = core.run(builder.buildComputer(), classes);
+            assertTrue(result.wasSuccessful());
+            Iterator<String> methods = NothingDoingTest1.methods.iterator();
+            for (Class<?> clazz : classes) {
+                String a = clazz.getName() + "#a()";
+                String b = clazz.getName() + "#b()";
+                assertThat(methods.next(), is("init"));
+                assertThat(methods.next(), anyOf(is(a), is(b)));
+                assertThat(methods.next(), anyOf(is(a), is(b)));
+                assertThat(methods.next(), is("deinit"));
+            }
+        }
+    }
+
+    @Test
+    public void keepBeforeAfterOneClass() {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder();
+        builder.parallelMethods();
+        testKeepBeforeAfter(builder, NothingDoingTest1.class);
+    }
+
+    @Test
+    public void keepBeforeAfterTwoClasses() {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder();
+        builder.useOnePool(5).parallelClasses(1).parallelMethods(2);
+        testKeepBeforeAfter(builder, NothingDoingTest1.class, NothingDoingTest2.class);
+    }
+
+    @Test
+    public void keepBeforeAfterTwoParallelClasses() {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder();
+        builder.useOnePool(8).parallelClasses(2).parallelMethods(2);
+        JUnitCore core = new JUnitCore();
+        NothingDoingTest1.methods.clear();
+        Class<?>[] classes = {NothingDoingTest1.class, NothingDoingTest2.class, NothingDoingTest3.class};
+        Result result = core.run(builder.buildComputer(), classes);
+        assertTrue(result.wasSuccessful());
+        ArrayList<String> methods = new ArrayList<String>(NothingDoingTest1.methods);
+        assertThat(methods.size(), is(12));
+        assertThat(methods.subList(9, 12), is(not(Arrays.asList("deinit", "deinit", "deinit"))));
+    }
+
+    private static volatile boolean beforeShutdown;
+    private static volatile Runnable shutdownTask;
+
+    public static class Class1 {
+        static volatile int concurrentMethods = 0;
+        static volatile int maxConcurrentMethods = 0;
+
+        @Test
+        public void test1() throws InterruptedException {
+            synchronized (Class1.class) {
+                ++concurrentMethods;
+                Class1.class.wait(500);
+                maxConcurrentMethods = Math.max(maxConcurrentMethods, concurrentMethods--);
+            }
+        }
+
+        @Test
+        public void test2() throws InterruptedException {
+            test1();
+            Runnable shutdownTask = ParallelComputerBuilderTest.shutdownTask;
+            if (shutdownTask != null) {
+                beforeShutdown = true;
+                shutdownTask.run();
+            }
+        }
+    }
+
+    public static class Class2 extends Class1 {
+    }
+
+    public static class Class3 extends Class1 {
+    }
+
+    @RunWith(Suite.class)
+    @Suite.SuiteClasses({Class2.class, Class1.class})
+    public class TestSuite {
+    }
+
+    public static class NothingDoingTest1 {
+        static final Collection<String> methods = new ConcurrentLinkedQueue<String>();
+
+        @BeforeClass
+        public static void init() {
+            methods.add("init");
+        }
+
+        @Test
+        public void a() throws InterruptedException {
+            Thread.sleep(5);
+            methods.add(getClass().getName() + "#a()");
+        }
+
+        @Test
+        public void b() throws InterruptedException {
+            Thread.sleep(5);
+            methods.add(getClass().getName() + "#b()");
+        }
+
+        @AfterClass
+        public static void deinit() {
+            methods.add("deinit");
+        }
+    }
+
+    public static class NothingDoingTest2 extends NothingDoingTest1 {
+    }
+
+    public static class NothingDoingTest3 extends NothingDoingTest1 {
+    }
+
+    @RunWith(Suite.class)
+    @Suite.SuiteClasses({NothingDoingTest1.class, NothingDoingTest2.class})
+    public static class NothingDoingSuite {
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/RangeMatcher.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/RangeMatcher.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/RangeMatcher.java
new file mode 100644
index 0000000..ad04d6b
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/RangeMatcher.java
@@ -0,0 +1,56 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+/**
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+final class RangeMatcher extends BaseMatcher<Long>
+{
+    private final long from;
+    private final long to;
+
+    private RangeMatcher( long from, long to )
+    {
+        this.from = from;
+        this.to = to;
+    }
+
+    public void describeTo( Description description )
+    {
+        description.appendValueList( "between ", " and ", "", from, to );
+    }
+
+    public static Matcher<Long> between( long from, long to )
+    {
+        return new RangeMatcher( from, to );
+    }
+
+    public boolean matches( Object o )
+    {
+        long actual = (Long) o;
+        return actual >= from && actual <= to;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategiesTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategiesTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategiesTest.java
new file mode 100644
index 0000000..485b63c
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategiesTest.java
@@ -0,0 +1,155 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests the factories in SchedulingStrategy.
+ *
+ * Th point of these tests is to check {@link Task#result} if changed
+ * from <code>false</code> to <code>true</code> after all scheduled tasks
+ * have finished.
+ * The call {@link SchedulingStrategy#finished()} is waiting until the
+ * strategy has finished.
+ * Then {@link Task#result} should be asserted that is <code>true</code>.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see SchedulingStrategy
+ */
+public class SchedulingStrategiesTest {
+    static class Task implements Runnable {
+        volatile boolean result = false;
+
+        public void run() {
+            result = true;
+        }
+    }
+
+    @Test
+    public void invokerStrategy() throws InterruptedException {
+        SchedulingStrategy strategy = SchedulingStrategies.createInvokerStrategy();
+        assertFalse(strategy.hasSharedThreadPool());
+        assertTrue(strategy.canSchedule());
+
+        Task task = new Task();
+
+        strategy.schedule(task);
+
+        assertTrue(strategy.canSchedule());
+
+        assertTrue(task.result);
+
+        assertTrue(strategy.finished());
+        assertFalse(strategy.canSchedule());
+    }
+
+    @Test
+    public void nonSharedPoolStrategy() throws InterruptedException {
+        SchedulingStrategy strategy = SchedulingStrategies.createParallelStrategy(2);
+        assertFalse(strategy.hasSharedThreadPool());
+        assertTrue(strategy.canSchedule());
+
+        Task task1 = new Task();
+        Task task2 = new Task();
+
+        strategy.schedule(task1);
+        strategy.schedule(task2);
+
+        assertTrue(strategy.canSchedule());
+
+        assertTrue(strategy.finished());
+        assertFalse(strategy.canSchedule());
+
+        assertTrue(task1.result);
+        assertTrue(task2.result);
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void sharedPoolStrategyNullPool() {
+        SchedulingStrategies.createParallelSharedStrategy(null);
+    }
+
+    @Test
+    public void sharedPoolStrategy() throws InterruptedException {
+        ExecutorService sharedPool = Executors.newCachedThreadPool();
+
+        SchedulingStrategy strategy1 = SchedulingStrategies.createParallelSharedStrategy(sharedPool);
+        assertTrue(strategy1.hasSharedThreadPool());
+        assertTrue(strategy1.canSchedule());
+
+        SchedulingStrategy strategy2 = SchedulingStrategies.createParallelSharedStrategy(sharedPool);
+        assertTrue(strategy2.hasSharedThreadPool());
+        assertTrue(strategy2.canSchedule());
+
+        Task task1 = new Task();
+        Task task2 = new Task();
+        Task task3 = new Task();
+        Task task4 = new Task();
+
+        strategy1.schedule(task1);
+        strategy2.schedule(task2);
+        strategy1.schedule(task3);
+        strategy2.schedule(task4);
+
+        assertTrue(strategy1.canSchedule());
+        assertTrue(strategy2.canSchedule());
+
+        assertTrue(strategy1.finished());
+        assertFalse(strategy1.canSchedule());
+
+        assertTrue(strategy2.finished());
+        assertFalse(strategy2.canSchedule());
+
+        assertTrue(task1.result);
+        assertTrue(task2.result);
+        assertTrue(task3.result);
+        assertTrue(task4.result);
+    }
+
+    @Test
+    public void infinitePoolStrategy() throws InterruptedException {
+        SchedulingStrategy strategy = SchedulingStrategies.createParallelStrategyUnbounded();
+        assertFalse(strategy.hasSharedThreadPool());
+        assertTrue(strategy.canSchedule());
+
+        Task task1 = new Task();
+        Task task2 = new Task();
+
+        strategy.schedule(task1);
+        strategy.schedule(task2);
+
+        assertTrue(strategy.canSchedule());
+
+        assertTrue(strategy.finished());
+        assertFalse(strategy.canSchedule());
+
+        assertTrue(task1.result);
+        assertTrue(task2.result);
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/Stopwatch.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/Stopwatch.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/Stopwatch.java
new file mode 100644
index 0000000..32b056c
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/Stopwatch.java
@@ -0,0 +1,45 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.rules.TestWatchman;
+import org.junit.runners.model.FrameworkMethod;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+final class Stopwatch extends TestWatchman
+{
+    private long startNanos;
+
+    long stop()
+    {
+        return TimeUnit.MILLISECONDS.convert(System.nanoTime() - startNanos, TimeUnit.NANOSECONDS);
+    }
+
+    @Override
+    public void starting(FrameworkMethod method)
+    {
+        startNanos = System.nanoTime();
+    }
+}


[2/3] [SUREFIRE-1021] New Parallel Computer for JUnit 4.7+ Submitted by: Tibor17

Posted by ag...@apache.org.
http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java
new file mode 100644
index 0000000..ef4fc94
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputer.java
@@ -0,0 +1,83 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.runner.Computer;
+import org.junit.runner.Description;
+
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * ParallelComputer extends JUnit {@link Computer} and has a shutdown functionality.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see ParallelComputerBuilder
+ */
+public abstract class ParallelComputer extends Computer
+{
+    private ScheduledExecutorService shutdownScheduler;
+
+    public abstract Collection<Description> shutdown( boolean shutdownNow );
+
+    protected final void afterRunQuietly()
+    {
+        if ( shutdownScheduler != null )
+        {
+            shutdownScheduler.shutdownNow();
+        }
+    }
+
+    public Future<Collection<Description>> scheduleShutdown( int timeout, TimeUnit unit )
+    {
+        return getShutdownScheduler().schedule( createShutdownTask( false ), timeout, unit );
+    }
+
+    public Future<Collection<Description>> scheduleForcedShutdown( int timeout, TimeUnit unit )
+    {
+        return getShutdownScheduler().schedule( createShutdownTask( true ), timeout, unit );
+    }
+
+    private ScheduledExecutorService getShutdownScheduler()
+    {
+        if ( shutdownScheduler == null )
+        {
+            shutdownScheduler = Executors.newScheduledThreadPool( 2 );
+        }
+        return shutdownScheduler;
+    }
+
+    private Callable<Collection<Description>> createShutdownTask( final boolean isForced )
+    {
+        return new Callable<Collection<Description>>()
+        {
+            public Collection<Description> call() throws Exception
+            {
+                return shutdown( isForced );
+            }
+        };
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java
new file mode 100644
index 0000000..ef2c05e
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerBuilder.java
@@ -0,0 +1,416 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.internal.runners.ErrorReportingRunner;
+import org.junit.runner.Description;
+import org.junit.runner.Runner;
+import org.junit.runner.manipulation.Filter;
+import org.junit.runner.manipulation.NoTestsRemainException;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.Suite;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerBuilder;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * Executing suites, classes and methods with defined concurrency. In this example the threads which completed
+ * the suites and classes can be reused in parallel methods.
+ * <pre>
+ * ParallelComputerBuilder builder = new ParallelComputerBuilder();
+ * builder.useOnePool(8).parallelSuites(2).parallelClasses(4).parallelMethods();
+ * ParallelComputerBuilder.ParallelComputer computer = builder.buildComputer();
+ * Class<?>[] tests = {...};
+ * new JUnitCore().run(computer, tests);
+ * </pre>
+ * Note that the type has always at least one thread even if unspecified. The capacity in
+ * {@link ParallelComputerBuilder#useOnePool(int)} must be greater than the number of concurrent suites and classes altogether.
+ * <p>
+ * The Computer can be shutdown in a separate thread. Pending tests will be interrupted if the argument is <tt>true</tt>.
+ * <pre>
+ * computer.shutdown(true);
+ * </pre>
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+public class ParallelComputerBuilder {
+    private static enum Type {
+        SUITES, CLASSES, METHODS
+    }
+
+    static final int TOTAL_POOL_SIZE_UNDEFINED = 0;
+    private final Map<Type, Integer> parallelGroups = new HashMap<Type, Integer>(3);
+    private boolean useSeparatePools;
+    private int totalPoolSize;
+
+    /**
+     * Calling {@link #useSeparatePools()}.
+     */
+    public ParallelComputerBuilder() {
+        useSeparatePools();
+        parallelGroups.put(Type.SUITES, 0);
+        parallelGroups.put(Type.CLASSES, 0);
+        parallelGroups.put(Type.METHODS, 0);
+    }
+
+    public ParallelComputerBuilder useSeparatePools() {
+        totalPoolSize = TOTAL_POOL_SIZE_UNDEFINED;
+        useSeparatePools = true;
+        return this;
+    }
+
+    public ParallelComputerBuilder useOnePool() {
+        totalPoolSize = TOTAL_POOL_SIZE_UNDEFINED;
+        useSeparatePools = false;
+        return this;
+    }
+
+    /**
+     * @param totalPoolSize Pool size where suites, classes and methods are executed in parallel.
+     *                      If the <tt>totalPoolSize</tt> is {@link Integer#MAX_VALUE}, the pool capacity is not limited.
+     * @throws IllegalArgumentException If <tt>totalPoolSize</tt> is &lt; 1.
+     */
+    public ParallelComputerBuilder useOnePool(int totalPoolSize) {
+        if (totalPoolSize < 1) {
+            throw new IllegalArgumentException("Size of common pool is less than 1.");
+        }
+        this.totalPoolSize = totalPoolSize;
+        useSeparatePools = false;
+        return this;
+    }
+
+    public ParallelComputerBuilder parallelSuites() {
+        return parallel(Type.SUITES);
+    }
+
+    public ParallelComputerBuilder parallelSuites(int nThreads) {
+        return parallel(nThreads, Type.SUITES);
+    }
+
+    public ParallelComputerBuilder parallelClasses() {
+        return parallel(Type.CLASSES);
+    }
+
+    public ParallelComputerBuilder parallelClasses(int nThreads) {
+        return parallel(nThreads, Type.CLASSES);
+    }
+
+    public ParallelComputerBuilder parallelMethods() {
+        return parallel(Type.METHODS);
+    }
+
+    public ParallelComputerBuilder parallelMethods(int nThreads) {
+        return parallel(nThreads, Type.METHODS);
+    }
+
+    private ParallelComputerBuilder parallel(int nThreads, Type parallelType) {
+        if (nThreads < 0) {
+            throw new IllegalArgumentException("negative nThreads " + nThreads);
+        }
+
+        if (parallelType == null) {
+            throw new NullPointerException("null parallelType");
+        }
+
+        parallelGroups.put(parallelType, nThreads);
+        return this;
+    }
+
+    private ParallelComputerBuilder parallel(Type parallelType) {
+        return parallel(Integer.MAX_VALUE, parallelType);
+    }
+
+    public ParallelComputer buildComputer() {
+        return new PC();
+    }
+
+    final class PC extends ParallelComputer
+    {
+        final Collection<ParentRunner> suites = new LinkedHashSet<ParentRunner>();
+        final Collection<ParentRunner> nestedSuites = new LinkedHashSet<ParentRunner>();
+        final Collection<ParentRunner> classes = new LinkedHashSet<ParentRunner>();
+        final Collection<ParentRunner> nestedClasses = new LinkedHashSet<ParentRunner>();
+        final Collection<Runner> unscheduledRunners = new LinkedHashSet<Runner>();
+        final int poolCapacity;
+        final boolean splitPool;
+        private final Map<Type, Integer> allGroups;
+        private volatile Scheduler master;
+
+        private PC() {
+            allGroups = new HashMap<Type, Integer>(ParallelComputerBuilder.this.parallelGroups);
+            poolCapacity = ParallelComputerBuilder.this.totalPoolSize;
+            splitPool = ParallelComputerBuilder.this.useSeparatePools;
+        }
+
+        @Override
+        public Collection<Description> shutdown(boolean shutdownNow) {
+            final Scheduler master = this.master;
+            return master == null ? Collections.<Description>emptyList() : master.shutdown(shutdownNow);
+        }
+
+        @Override
+        public Runner getSuite(RunnerBuilder builder, Class<?>[] cls) throws InitializationError {
+            super.getSuite(builder, cls);
+            populateChildrenFromSuites();
+            return setSchedulers();
+        }
+
+        @Override
+        protected Runner getRunner( RunnerBuilder builder, Class<?> testClass ) throws Throwable
+        {
+            Runner runner = super.getRunner( builder, testClass );
+            if ( canSchedule(runner) )
+            {
+                if ( runner instanceof Suite )
+                {
+                    suites.add( (Suite) runner );
+                }
+                else
+                {
+                    classes.add( (ParentRunner) runner );
+                }
+            }
+            else
+            {
+                unscheduledRunners.add( runner );
+            }
+            return runner;
+        }
+
+        private class SuiteFilter extends Filter {
+            @Override
+            public boolean shouldRun(Description description) {
+                return true;
+            }
+
+            @Override
+            public void apply(Object child) throws NoTestsRemainException {
+                super.apply(child);
+                if (child instanceof Suite) {
+                    nestedSuites.add((Suite) child);
+                } else if (child instanceof ParentRunner) {
+                    nestedClasses.add((ParentRunner) child);
+                }
+            }
+
+            @Override
+            public String describe() {
+                return "";
+            }
+        }
+
+        private <T extends Runner> ParentRunner wrapRunners( Collection<T> runners ) throws InitializationError {
+            ArrayList<Runner> runs = new ArrayList<Runner>();
+            for ( T runner : runners )
+            {
+                if ( runner != null && hasChildren( runner ) )
+                {
+                    runs.add( runner );
+                }
+            }
+
+            return runs.isEmpty() ? null : new Suite( null, runs ) {};
+        }
+
+        private boolean hasChildren( Runner runner )
+        {
+            Description description = runner.getDescription();
+            Collection children = description == null ? null : description.getChildren();
+            return children != null && !children.isEmpty();
+        }
+
+        private ExecutorService createPool(int poolSize) {
+            return poolSize < Integer.MAX_VALUE ? Executors.newFixedThreadPool(poolSize) : Executors.newCachedThreadPool();
+        }
+
+        private Scheduler createMaster(ExecutorService pool, int poolSize) {
+            if (!areSuitesAndClassesParallel() || poolSize <= 1) {
+                return new Scheduler(null, new InvokerStrategy());
+            } else if (pool != null && poolSize == Integer.MAX_VALUE) {
+                return new Scheduler(null, new SharedThreadPoolStrategy(pool));
+            } else {
+                return new Scheduler(null, SchedulingStrategies.createParallelStrategy(2));
+            }
+        }
+
+        private boolean areSuitesAndClassesParallel() {
+            return !suites.isEmpty() && allGroups.get(Type.SUITES) > 0 && !classes.isEmpty() && allGroups.get(Type.CLASSES) > 0;
+        }
+
+        private void populateChildrenFromSuites() {
+            Filter filter = new SuiteFilter();
+            for (Iterator<ParentRunner> it = suites.iterator(); it.hasNext();) {
+                ParentRunner suite = it.next();
+                try {
+                    suite.filter(filter);
+                } catch (NoTestsRemainException e) {
+                    it.remove();
+                }
+            }
+        }
+
+        private int totalPoolSize() {
+            if (poolCapacity == TOTAL_POOL_SIZE_UNDEFINED) {
+                int total = 0;
+                for (int nThreads : allGroups.values()) {
+                    total += nThreads;
+                    if (total < 0) {
+                        total = Integer.MAX_VALUE;
+                        break;
+                    }
+                }
+                return total;
+            } else {
+                return poolCapacity;
+            }
+        }
+
+        private Runner setSchedulers() throws InitializationError {
+            int parallelSuites = allGroups.get(Type.SUITES);
+            int parallelClasses = allGroups.get(Type.CLASSES);
+            int parallelMethods = allGroups.get(Type.METHODS);
+            int poolSize = totalPoolSize();
+            ExecutorService commonPool = splitPool || poolSize == 0 ? null : createPool(poolSize);
+            master = createMaster(commonPool, poolSize);
+
+            ParentRunner suiteSuites = wrapRunners( suites );
+            if ( suiteSuites != null )
+            {
+                // a scheduler for parallel suites
+                if ( commonPool != null && parallelSuites > 0 )
+                {
+                    Balancer balancer = BalancerFactory.createBalancerWithFairness( parallelSuites );
+                    suiteSuites.setScheduler( createScheduler( null, commonPool, true, balancer ) );
+                }
+                else
+                {
+                    suiteSuites.setScheduler( createScheduler( parallelSuites ) );
+                }
+            }
+
+            // schedulers for parallel classes
+            ParentRunner suiteClasses = wrapRunners( classes );
+            ArrayList<ParentRunner> allSuites = new ArrayList<ParentRunner>( suites );
+            allSuites.addAll( nestedSuites );
+            if ( suiteClasses != null )
+            {
+                allSuites.add( suiteClasses );
+            }
+            if ( !allSuites.isEmpty() )
+            {
+                setSchedulers( allSuites, parallelClasses, commonPool );
+            }
+
+            // schedulers for parallel methods
+            ArrayList<ParentRunner> allClasses = new ArrayList<ParentRunner>( classes );
+            allClasses.addAll( nestedClasses );
+            if ( !allClasses.isEmpty() )
+            {
+                setSchedulers( allClasses, parallelMethods, commonPool );
+            }
+
+            // resulting runner for Computer#getSuite() scheduled by master scheduler
+            ParentRunner all = createFinalRunner( suiteSuites, suiteClasses );
+            all.setScheduler( master );
+            return all;
+        }
+
+        private ParentRunner createFinalRunner( Runner... runners ) throws InitializationError
+        {
+            ArrayList<Runner> all = new ArrayList<Runner>( unscheduledRunners );
+            for ( Runner runner : runners )
+            {
+                if ( runner != null )
+                {
+                    all.add( runner );
+                }
+            }
+
+            return new Suite( null, all )
+            {
+                @Override
+                public void run( RunNotifier notifier )
+                {
+                    try
+                    {
+                        super.run( notifier );
+                    }
+                    finally
+                    {
+                        afterRunQuietly();
+                    }
+                }
+            };
+        }
+
+        private void setSchedulers(Iterable<? extends ParentRunner> runners, int poolSize, ExecutorService commonPool) {
+            if (commonPool != null) {
+                Balancer concurrencyLimit = BalancerFactory.createBalancerWithFairness(poolSize);
+                boolean doParallel = poolSize > 0;
+                for (ParentRunner runner : runners) {
+                    runner.setScheduler(createScheduler(runner.getDescription(), commonPool, doParallel, concurrencyLimit));
+                }
+            } else {
+                ExecutorService pool = null;
+                if (poolSize == Integer.MAX_VALUE) {
+                    pool = Executors.newCachedThreadPool();
+                } else if (poolSize > 0) {
+                    pool = Executors.newFixedThreadPool(poolSize);
+                }
+                boolean doParallel = pool != null;
+                for (ParentRunner runner : runners) {
+                    runner.setScheduler(createScheduler(runner.getDescription(), pool, doParallel,
+                            BalancerFactory.createInfinitePermitsBalancer()));
+                }
+            }
+        }
+
+        private Scheduler createScheduler(Description desc, ExecutorService pool, boolean doParallel, Balancer concurrency) {
+            doParallel &= pool != null;
+            SchedulingStrategy strategy = doParallel ? new SharedThreadPoolStrategy(pool) : new InvokerStrategy();
+            return new Scheduler(desc, master, strategy, concurrency);
+        }
+
+        private Scheduler createScheduler(int poolSize) {
+            if (poolSize == Integer.MAX_VALUE) {
+                return new Scheduler(null, master, SchedulingStrategies.createParallelStrategyUnbounded());
+            } else if (poolSize == 0) {
+                return new Scheduler(null, master, new InvokerStrategy());
+            } else {
+                return new Scheduler(null, master, SchedulingStrategies.createParallelStrategy(poolSize));
+            }
+        }
+
+        private boolean canSchedule(Runner runner) {
+            return !(runner instanceof ErrorReportingRunner) && runner instanceof ParentRunner;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java
new file mode 100644
index 0000000..7b287e5
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Scheduler.java
@@ -0,0 +1,335 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.runner.Description;
+import org.junit.runners.model.RunnerScheduler;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.RejectedExecutionHandler;
+import java.util.concurrent.ThreadPoolExecutor;
+
+/**
+ *
+ * Schedules tests, controls thread resources, awaiting tests and other schedulers finished, and
+ * a master scheduler can shutdown slaves.
+ * <p>
+ * The scheduler objects should be first created (and wired) and set in runners
+ * {@link org.junit.runners.ParentRunner#setScheduler(org.junit.runners.model.RunnerScheduler)}.
+ * <p>
+ * A new instance of scheduling strategy should be passed to the constructor of this scheduler.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+public class Scheduler implements RunnerScheduler {
+    private final Balancer balancer;
+    private final SchedulingStrategy strategy;
+    private final Set<Controller> slaves = new CopyOnWriteArraySet<Controller>();
+    private final Description description;
+    private volatile boolean shutdown = false;
+    private volatile boolean started = false;
+    private volatile Controller masterController;
+
+    /**
+     * Use e.g. parallel classes have own non-shared thread pool, and methods another pool.
+     * <p>
+     * You can use it with one infinite thread pool shared in strategies across all
+     * suites, class runners, etc.
+     */
+    public Scheduler(Description description, SchedulingStrategy strategy) {
+        this(description, strategy, -1);
+    }
+
+    /**
+     * Should be used if schedulers in parallel children and parent use one instance of bounded thread pool.
+     * <p>
+     * Set this scheduler in a e.g. one suite of classes, then every individual class runner should reference
+     * {@link #Scheduler(org.junit.runner.Description, Scheduler, SchedulingStrategy)}
+     * or {@link #Scheduler(org.junit.runner.Description, Scheduler, SchedulingStrategy, int)}.
+     *
+     * @param description description of current runner
+     * @param strategy scheduling strategy with a shared thread pool
+     * @param concurrency determines maximum concurrent children scheduled a time via {@link #schedule(Runnable)}
+     * @throws NullPointerException if null <tt>strategy</tt>
+     */
+    public Scheduler(Description description, SchedulingStrategy strategy, int concurrency) {
+        this(description, strategy, BalancerFactory.createBalancer(concurrency));
+    }
+
+    /**
+     * New instances should be used by schedulers with limited concurrency by <tt>balancer</tt>
+     * against other groups of schedulers. The schedulers share one pool.
+     * <p>
+     * Unlike in {@link #Scheduler(org.junit.runner.Description, SchedulingStrategy, int)} which was limiting
+     * the <tt>concurrency</tt> of children of a runner where this scheduler was set, <em>this</em> <tt>balancer</tt>
+     * is limiting the concurrency of all children in runners having schedulers created by this constructor.
+     *
+     * @param description description of current runner
+     * @param strategy scheduling strategy which may share threads with other strategy
+     * @param balancer determines maximum concurrent children scheduled a time via {@link #schedule(Runnable)}
+     * @throws NullPointerException if null <tt>strategy</tt> or <tt>balancer</tt>
+     */
+    public Scheduler(Description description, SchedulingStrategy strategy, Balancer balancer) {
+        strategy.setDefaultShutdownHandler(newShutdownHandler());
+        this.description = description;
+        this.strategy = strategy;
+        this.balancer = balancer;
+        masterController = null;
+    }
+    /**
+     * Can be used by e.g. a runner having parallel classes in use case with parallel
+     * suites, classes and methods sharing the same thread pool.
+     *
+     * @param description description of current runner
+     * @param masterScheduler scheduler sharing own threads with this slave
+     * @param strategy scheduling strategy for this scheduler
+     * @param balancer determines maximum concurrent children scheduled a time via {@link #schedule(Runnable)}
+     * @throws NullPointerException if null <tt>masterScheduler</tt>, <tt>strategy</tt> or <tt>balancer</tt>
+     */
+    public Scheduler(Description description, Scheduler masterScheduler, SchedulingStrategy strategy, Balancer balancer) {
+        this(description, strategy, balancer);
+        strategy.setDefaultShutdownHandler(newShutdownHandler());
+        masterScheduler.register(this);
+    }
+
+    /**
+     * @param masterScheduler a reference to {@link #Scheduler(org.junit.runner.Description, SchedulingStrategy, int)}
+     *                        or {@link #Scheduler(org.junit.runner.Description, SchedulingStrategy)}
+     * @see #Scheduler(org.junit.runner.Description, SchedulingStrategy)
+     * @see #Scheduler(org.junit.runner.Description, SchedulingStrategy, int)
+     */
+    public Scheduler(Description description, Scheduler masterScheduler, SchedulingStrategy strategy, int concurrency) {
+        this(description, strategy, concurrency);
+        strategy.setDefaultShutdownHandler(newShutdownHandler());
+        masterScheduler.register(this);
+    }
+
+    /**
+     * Should be used with individual pools on suites, classes and methods, see
+     * {@link org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilder#useSeparatePools()}.
+     * <p>
+     * Cached thread pool is infinite and can be always shared.
+     */
+    public Scheduler(Description description, Scheduler masterScheduler, SchedulingStrategy strategy) {
+        this(description, masterScheduler, strategy, 0);
+    }
+
+    private void setController(Controller masterController) {
+        if (masterController == null) {
+            throw new NullPointerException("null ExecutionController");
+        }
+        this.masterController = masterController;
+    }
+
+    /**
+     * @param slave a slave scheduler to register
+     * @return <tt>true</tt> if successfully registered the <tt>slave</tt>.
+     */
+    private boolean register(Scheduler slave) {
+        boolean canRegister = slave != null && slave != this;
+        if (canRegister) {
+            Controller controller = new Controller(slave);
+            canRegister = !slaves.contains(controller);
+            if (canRegister) {
+                slaves.add(controller);
+                slave.setController(controller);
+            }
+        }
+        return canRegister;
+    }
+
+    /**
+     * @return <tt>true</tt> if new tasks can be scheduled.
+     */
+    private boolean canSchedule() {
+        return !shutdown && (masterController == null || masterController.canSchedule());
+    }
+
+    protected void logQuietly(Throwable t) {
+        t.printStackTrace(System.err);
+    }
+
+    protected void logQuietly(String msg) {
+        System.err.println(msg);
+    }
+
+    /**
+     * Attempts to stop all actively executing tasks and immediately returns a collection
+     * of descriptions of those tasks which have started prior to this call.
+     * <p>
+     * This scheduler and other registered schedulers will shutdown, see {@link #register(Scheduler)}.
+     * If <tt>shutdownNow</tt> is set, waiting methods will be interrupted via {@link Thread#interrupt}.
+     *
+     * @param shutdownNow if <tt>true</tt> interrupts waiting methods
+     * @return collection of descriptions started before shutting down
+     */
+    public Collection<Description> shutdown(boolean shutdownNow) {
+        shutdown = true;
+        ArrayList<Description> activeChildren = new ArrayList<Description>();
+
+        if (started && description != null) {
+            activeChildren.add(description);
+        }
+
+        for (Controller slave : slaves) {
+            try {
+                activeChildren.addAll(slave.shutdown(shutdownNow));
+            } catch (Throwable t) {
+                logQuietly(t);
+            }
+        }
+
+        try {
+            balancer.releaseAllPermits();
+        } finally {
+            if (shutdownNow) {
+                strategy.stopNow();
+            } else {
+                strategy.stop();
+            }
+        }
+
+        return activeChildren;
+    }
+
+    protected void beforeExecute() {
+    }
+
+    protected void afterExecute() {
+    }
+
+    public void schedule(Runnable childStatement) {
+        if (childStatement == null) {
+            logQuietly("cannot schedule null");
+        } else if (canSchedule() && strategy.canSchedule()) {
+            try {
+                balancer.acquirePermit();
+                Runnable task = wrapTask(childStatement);
+                strategy.schedule(task);
+                started = true;
+            } catch (RejectedExecutionException e) {
+                shutdown(false);
+            } catch (Throwable t) {
+                balancer.releasePermit();
+                logQuietly(t);
+            }
+        }
+    }
+
+    public void finished() {
+        try {
+            strategy.finished();
+        } catch (InterruptedException e) {
+            logQuietly(e);
+        } finally {
+            for (Controller slave : slaves) {
+                slave.awaitFinishedQuietly();
+            }
+        }
+    }
+
+    private Runnable wrapTask(final Runnable task) {
+        return new Runnable() {
+            public void run() {
+                try {
+                    beforeExecute();
+                    task.run();
+                } finally {
+                    try {
+                        afterExecute();
+                    } finally {
+                        balancer.releasePermit();
+                    }
+                }
+            }
+        };
+    }
+
+    protected ShutdownHandler newShutdownHandler() {
+        return new ShutdownHandler();
+    }
+
+    /**
+     * If this is a master scheduler, the slaves can stop scheduling by the master through the controller.
+     */
+    private final class Controller {
+        private final Scheduler slave;
+
+        private Controller(Scheduler slave) {
+            this.slave = slave;
+        }
+
+        /**
+         * @return <tt>true</tt> if new children can be scheduled.
+         */
+        boolean canSchedule() {
+            return Scheduler.this.canSchedule();
+        }
+
+        void awaitFinishedQuietly() {
+            try {
+                slave.finished();
+            } catch(Throwable t) {
+                slave.logQuietly(t);
+            }
+        }
+
+        Collection<Description> shutdown(boolean shutdownNow) {
+            return slave.shutdown(shutdownNow);
+        }
+
+        @Override
+        public int hashCode() {
+            return slave.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            return o == this || (o instanceof Controller) && slave.equals(((Controller) o).slave);
+        }
+    }
+
+    public class ShutdownHandler implements RejectedExecutionHandler {
+        private volatile RejectedExecutionHandler poolHandler;
+
+        protected ShutdownHandler() {
+            poolHandler = null;
+        }
+
+        public void setRejectedExecutionHandler(RejectedExecutionHandler poolHandler) {
+            this.poolHandler = poolHandler;
+        }
+
+        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
+            if (executor.isShutdown()) {
+                shutdown(false);
+            }
+            final RejectedExecutionHandler poolHandler = this.poolHandler;
+            if (poolHandler != null) {
+                poolHandler.rejectedExecution(r, executor);
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategies.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategies.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategies.java
new file mode 100644
index 0000000..4d3c6a6
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategies.java
@@ -0,0 +1,73 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/**
+ * The factory of {@link SchedulingStrategy}.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+public class SchedulingStrategies {
+
+    /**
+     * @return sequentially executing strategy
+     */
+    public static SchedulingStrategy createInvokerStrategy() {
+        return new InvokerStrategy();
+    }
+
+    /**
+     * @param nThreads fixed pool capacity
+     * @return parallel scheduling strategy
+     */
+    public static SchedulingStrategy createParallelStrategy(int nThreads) {
+        return new NonSharedThreadPoolStrategy(Executors.newFixedThreadPool(nThreads));
+    }
+
+    /**
+     * @return parallel scheduling strategy with unbounded capacity
+     */
+    public static SchedulingStrategy createParallelStrategyUnbounded() {
+        return new NonSharedThreadPoolStrategy(Executors.newCachedThreadPool());
+    }
+
+    /**
+     * The <tt>threadPool</tt> passed to this strategy can be shared in other strategies.
+     * <p>
+     * The call {@link SchedulingStrategy#finished()} is waiting until own tasks have finished.
+     * New tasks will not be scheduled by this call in this strategy. This strategy is not
+     * waiting for other strategies to finish. The {@link org.junit.runners.model.RunnerScheduler#finished()} may
+     * freely use {@link SchedulingStrategy#finished()}.
+     *
+     * @param threadPool thread pool possibly shared with other strategies
+     * @return parallel strategy with shared thread pool
+     * @throws NullPointerException if <tt>threadPool</tt> is null
+     */
+    public static SchedulingStrategy createParallelSharedStrategy(ExecutorService threadPool) {
+        if (threadPool == null) {
+            throw new NullPointerException("null threadPool in #createParallelSharedStrategy");
+        }
+        return new SharedThreadPoolStrategy(threadPool);
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java
new file mode 100644
index 0000000..c9da764
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SchedulingStrategy.java
@@ -0,0 +1,105 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.runners.model.RunnerScheduler;
+
+import java.util.concurrent.RejectedExecutionException;
+
+/**
+ * Specifies the strategy of scheduling whether sequential, or parallel.
+ * The strategy may use a thread pool <em>shared</em> with other strategies.
+ * <p/>
+ * One instance of strategy can be used just by one {@link Scheduler}.
+ * <p/>
+ * The strategy is scheduling tasks in {@link #schedule(Runnable)} and awaiting them
+ * completed in {@link #finished()}. Both methods should be used in one thread.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+public abstract class SchedulingStrategy {
+
+    /**
+     * Schedules tasks if {@link #canSchedule()}.
+     *
+     * @param task runnable to schedule in a thread pool or invoke
+     * @throws RejectedExecutionException if <tt>task</tt>
+     *         cannot be scheduled for execution
+     * @throws NullPointerException if <tt>task</tt> is <tt>null</tt>
+     * @see RunnerScheduler#schedule(Runnable)
+     * @see java.util.concurrent.Executor#execute(Runnable)
+     */
+    protected abstract void schedule(Runnable task);
+
+    /**
+     * Waiting for scheduled tasks to finish.
+     * New tasks will not be scheduled by calling this method.
+     *
+     * @return <tt>true</tt> if successfully stopped the scheduler, else
+     *         <tt>false</tt> if already stopped (a <em>shared</em> thread
+     *         pool was shutdown externally).
+     * @throws InterruptedException if interrupted while waiting
+     *         for scheduled tasks to finish
+     * @see RunnerScheduler#finished()
+     */
+    protected abstract boolean finished() throws InterruptedException;
+
+    /**
+     * Stops scheduling new tasks (e.g. by {@link java.util.concurrent.ExecutorService#shutdown()}
+     * on a private thread pool which cannot be <em>shared</em> with other strategy).
+     *
+     * @return <tt>true</tt> if successfully stopped the scheduler, else
+     *         <tt>false</tt> if already stopped (a <em>shared</em> thread
+     *         pool was shutdown externally).
+     * @see java.util.concurrent.ExecutorService#shutdown()
+     */
+    protected abstract boolean stop();
+
+    /**
+     * Stops scheduling new tasks and <em>interrupts</em> running tasks
+     * (e.g. by {@link java.util.concurrent.ExecutorService#shutdownNow()} on a private thread pool
+     * which cannot be <em>shared</em> with other strategy).
+     * <p>
+     * This method calls {@link #stop()} by default.
+     *
+     * @return <tt>true</tt> if successfully stopped the scheduler, else
+     *         <tt>false</tt> if already stopped (a <em>shared</em> thread
+     *         pool was shutdown externally).
+     * @see java.util.concurrent.ExecutorService#shutdownNow()
+     */
+    protected boolean stopNow() {
+        return stop();
+    }
+
+    protected void setDefaultShutdownHandler(Scheduler.ShutdownHandler handler) {
+    }
+
+    /**
+     * @return <tt>true</tt> if a thread pool associated with this strategy
+     * can be shared with other strategies.
+     */
+    protected abstract boolean hasSharedThreadPool();
+
+    /**
+     * @return <tt>true</tt> unless stopped or finished.
+     */
+    protected abstract boolean canSchedule();
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java
new file mode 100644
index 0000000..53082c4
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SharedThreadPoolStrategy.java
@@ -0,0 +1,84 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.util.concurrent.CancellationException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+/**
+ * Parallel strategy for shared thread pool in private package.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see AbstractThreadPoolStrategy
+ */
+final class SharedThreadPoolStrategy extends AbstractThreadPoolStrategy {
+    SharedThreadPoolStrategy(ExecutorService threadPool) {
+        super(threadPool, new ConcurrentLinkedQueue<Future<?>>());
+    }
+
+    @Override
+    public boolean hasSharedThreadPool() {
+        return true;
+    }
+
+    @Override
+    public boolean finished() throws InterruptedException {
+        boolean wasRunningAll = canSchedule();
+        for (Future<?> futureResult : getFutureResults()) {
+            try {
+                futureResult.get();
+            } catch (InterruptedException e) {
+                // after called external ExecutorService#shutdownNow()
+                // or ExecutorService#shutdown()
+                wasRunningAll = false;
+            } catch (ExecutionException e) {
+                // test throws exception
+            } catch (CancellationException e) {
+                // cannot happen because not calling Future#cancel()
+            }
+        }
+        disable();
+        return wasRunningAll;
+    }
+
+    @Override
+    protected final boolean stop() {
+        return stop(false);
+    }
+
+    @Override
+    protected final boolean stopNow() {
+        return stop(true);
+    }
+
+    private boolean stop(boolean interrupt) {
+        final boolean wasRunning = canSchedule();
+        for (Future<?> futureResult : getFutureResults()) {
+            futureResult.cancel(interrupt);
+        }
+        disable();
+        return wasRunning;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java
new file mode 100644
index 0000000..80d116d
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ThreadResourcesBalancer.java
@@ -0,0 +1,90 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.util.concurrent.Semaphore;
+
+/**
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see Balancer
+ */
+final class ThreadResourcesBalancer implements Balancer
+{
+    private final Semaphore balancer;
+    private final int numPermits;
+
+    /**
+     * <tt>fair</tt> set to false.
+     *
+     * @param numPermits number of permits to acquire when maintaining concurrency on tests.
+     *                   Must be &gt;0 and &lt; {@link Integer#MAX_VALUE}.
+     *
+     * @see #ThreadResourcesBalancer(int, boolean)
+     */
+    ThreadResourcesBalancer( int numPermits )
+    {
+        this( numPermits, false );
+    }
+
+    /**
+     * @param numPermits number of permits to acquire when maintaining concurrency on tests.
+     *                   Must be &gt;0 and &lt; {@link Integer#MAX_VALUE}.
+     * @param fair <tt>true</tt> guarantees the waiting schedulers to wake up in order they acquired a permit
+     */
+    ThreadResourcesBalancer( int numPermits, boolean fair )
+    {
+        balancer = new Semaphore( numPermits, fair );
+        this.numPermits = numPermits;
+    }
+
+    /**
+     * Acquires a permit from this balancer, blocking until one is available.
+     *
+     * @return <code>true</code> if current thread is <em>NOT</em> interrupted
+     *         while waiting for a permit.
+     */
+    public boolean acquirePermit()
+    {
+        try
+        {
+            balancer.acquire();
+            return true;
+        }
+        catch ( InterruptedException e )
+        {
+            return false;
+        }
+    }
+
+    /**
+     * Releases a permit, returning it to the balancer.
+     */
+    public void releasePermit()
+    {
+        balancer.release();
+    }
+
+    public void releaseAllPermits()
+    {
+        balancer.release( numPermits );
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit4SuiteTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit4SuiteTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit4SuiteTest.java
new file mode 100644
index 0000000..455f280
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnit4SuiteTest.java
@@ -0,0 +1,49 @@
+package org.apache.maven.surefire.junitcore;
+
+/*
+ * 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 junit.framework.JUnit4TestAdapter;
+import junit.framework.Test;
+import org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilderTest;
+import org.apache.maven.surefire.junitcore.pc.SchedulingStrategiesTest;
+import org.junit.runner.RunWith;
+import org.junit.runners.Suite;
+
+/**
+ * Adapt the JUnit4 tests which use only annotations to the JUnit3 test suite.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+@Suite.SuiteClasses( {
+        Surefire746Test.class,
+        Surefire813IncorrectResultTest.class,
+        ParallelComputerFactoryTest.class,
+        ParallelComputerBuilderTest.class,
+        SchedulingStrategiesTest.class
+} )
+@RunWith( Suite.class )
+public class JUnit4SuiteTest
+{
+    public static Test suite()
+    {
+        return new JUnit4TestAdapter( JUnit4SuiteTest.class );
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreParametersTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreParametersTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreParametersTest.java
index 0201d90..e100357 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreParametersTest.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/JUnitCoreParametersTest.java
@@ -34,7 +34,7 @@ public class JUnitCoreParametersTest
     {
         assertFalse( getTestSetClasses().isParallelMethod() );
         assertTrue( getTestSetMethods().isParallelMethod() );
-        assertFalse( getTestSetBoth().isParallelMethod() );
+        assertTrue( getTestSetBoth().isParallelMethod() );
     }
 
     public void testIsParallelClasses()
@@ -42,15 +42,15 @@ public class JUnitCoreParametersTest
     {
         assertTrue( getTestSetClasses().isParallelClasses() );
         assertFalse( getTestSetMethods().isParallelClasses() );
-        assertFalse( getTestSetBoth().isParallelClasses() );
+        assertTrue( getTestSetBoth().isParallelClasses() );
     }
 
     public void testIsParallelBoth()
         throws Exception
     {
-        assertFalse( getTestSetClasses().isParallelBoth() );
-        assertFalse( getTestSetMethods().isParallelBoth() );
-        assertTrue( getTestSetBoth().isParallelBoth() );
+        assertFalse( isParallelMethodsAndClasses( getTestSetClasses() ) );
+        assertFalse( isParallelMethodsAndClasses( getTestSetMethods() ) );
+        assertTrue( isParallelMethodsAndClasses( getTestSetBoth() ) );
     }
 
     public void testIsPerCoreThreadCount()
@@ -145,4 +145,9 @@ public class JUnitCoreParametersTest
     {
         return new JUnitCoreParameters( getPropsetMethods() );
     }
+
+    private boolean isParallelMethodsAndClasses( JUnitCoreParameters jUnitCoreParameters )
+    {
+        return jUnitCoreParameters.isParallelMethod() && jUnitCoreParameters.isParallelClasses();
+    }
 }


[3/3] git commit: [SUREFIRE-1021] New Parallel Computer for JUnit 4.7+ Submitted by: Tibor17

Posted by ag...@apache.org.
[SUREFIRE-1021] New Parallel Computer for JUnit 4.7+
Submitted by: Tibor17

o Applied without any changes (just rebased w/o any conflicts and squashed)


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

Branch: refs/heads/master
Commit: 49c4a62597383317a6d4abd8320d27d4a7cba354
Parents: e224e8b
Author: Tibor Digana <ti...@lycos.com>
Authored: Wed Jul 31 20:42:16 2013 +0200
Committer: Andreas Gudian <ag...@apache.org>
Committed: Fri Aug 9 21:18:18 2013 +0200

----------------------------------------------------------------------
 .../plugin/failsafe/IntegrationTestMojo.java    |  37 +
 .../plugin/surefire/AbstractSurefireMojo.java   | 275 +++++-
 .../surefire/SurefireExecutionParameters.java   |   8 +
 .../maven/plugin/surefire/SurefirePlugin.java   |  37 +
 .../surefire/booter/BaseProviderFactory.java    |   2 +-
 .../surefire/booter/ProviderParameterNames.java |  10 +
 .../surefire/its/Junit47WithCucumberIT.java     |   7 +-
 .../maven/surefire/its/TestSingleMethodIT.java  |   6 +-
 .../surefire/its/fixture/SurefireLauncher.java  |   5 +
 .../its/jiras/Surefire943ReportContentIT.java   |   9 +-
 .../src/test/resources/junit44-dep/pom.xml      |   1 +
 .../src/test/java/junit44Dep/BasicTest.java     |   1 +
 .../surefire/junitcore/JUnitCoreParameters.java |  99 +-
 .../surefire/junitcore/JUnitCoreProvider.java   |  17 +-
 .../surefire/junitcore/JUnitCoreWrapper.java    | 166 +++-
 .../junitcore/ParallelComputerFactory.java      | 368 ++++++++
 .../pc/AbstractThreadPoolStrategy.java          | 111 +++
 .../maven/surefire/junitcore/pc/Balancer.java   |  49 +
 .../surefire/junitcore/pc/BalancerFactory.java  |  68 ++
 .../surefire/junitcore/pc/InvokerStrategy.java  |  61 ++
 .../pc/NonSharedThreadPoolStrategy.java         |  54 ++
 .../surefire/junitcore/pc/NullBalancer.java     |  44 +
 .../surefire/junitcore/pc/ParallelComputer.java |  83 ++
 .../junitcore/pc/ParallelComputerBuilder.java   | 416 +++++++++
 .../maven/surefire/junitcore/pc/Scheduler.java  | 335 +++++++
 .../junitcore/pc/SchedulingStrategies.java      |  73 ++
 .../junitcore/pc/SchedulingStrategy.java        | 105 +++
 .../junitcore/pc/SharedThreadPoolStrategy.java  |  84 ++
 .../junitcore/pc/ThreadResourcesBalancer.java   |  90 ++
 .../surefire/junitcore/JUnit4SuiteTest.java     |  49 +
 .../junitcore/JUnitCoreParametersTest.java      |  15 +-
 .../junitcore/ParallelComputerFactoryTest.java  | 907 +++++++++++++++++++
 .../surefire/junitcore/Surefire746Test.java     |  25 +-
 .../pc/ParallelComputerBuilderTest.java         | 436 +++++++++
 .../surefire/junitcore/pc/RangeMatcher.java     |  56 ++
 .../junitcore/pc/SchedulingStrategiesTest.java  | 155 ++++
 .../maven/surefire/junitcore/pc/Stopwatch.java  |  45 +
 37 files changed, 4219 insertions(+), 90 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
----------------------------------------------------------------------
diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
index 23f8dc1..41278c3 100644
--- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
+++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java
@@ -137,6 +137,27 @@ public class IntegrationTestMojo
      */
     @Parameter( property = "failsafe.timeout" )
     private int forkedProcessTimeoutInSeconds;
+
+    /**
+     * Stop executing queued parallel JUnit tests after a certain number of seconds.
+     * If set to 0, wait forever, never timing out.
+     * Makes sense with specified <code>parallel</code> different from "none".
+     *
+     * @since 2.16
+     */
+    @Parameter( property = "failsafe.parallel.timeout" )
+    private int parallelTestsTimeoutInSeconds;
+
+    /**
+     * Stop executing queued parallel JUnit tests
+     * and <em>interrupt</em> currently running tests after a certain number of seconds.
+     * If set to 0, wait forever, never timing out.
+     * Makes sense with specified <code>parallel</code> different from "none".
+     *
+     * @since 2.16
+     */
+    @Parameter( property = "failsafe.parallel.forcedTimeout" )
+    private int parallelTestsTimeoutForcedInSeconds;
     
     /**
      * A list of &lt;include> elements specifying the tests (by pattern) that should be included in testing. When not
@@ -433,6 +454,22 @@ public class IntegrationTestMojo
         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
     }
 
+    public int getParallelTestsTimeoutInSeconds() {
+        return parallelTestsTimeoutInSeconds;
+    }
+
+    public void setParallelTestsTimeoutInSeconds( int parallelTestsTimeoutInSeconds ) {
+        this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds;
+    }
+
+    public int getParallelTestsTimeoutForcedInSeconds() {
+        return parallelTestsTimeoutForcedInSeconds;
+    }
+
+    public void setParallelTestsTimeoutForcedInSeconds( int parallelTestsTimeoutForcedInSeconds ) {
+        this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds;
+    }
+
     public boolean isUseSystemClassLoader()
     {
         return useSystemClassLoader;

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/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 677920e..5975d7d 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
@@ -483,7 +483,13 @@ public abstract class AbstractSurefireMojo
      * respect their order of execution.
      * <p/>
      * (JUnit 4.7 provider) Supports values "classes"/"methods"/"both" to run in separate threads, as controlled by
-     * <code>threadCount</code>.
+     * <code>threadCount</code>.<br/>
+     * <br/>
+     * Since version 2.16 (JUnit 4.7 provider), the value "both" is <strong>DEPRECATED</strong>.
+     * Use <strong>"classesAndMethods"</strong> instead.<br/>
+     * <br/>
+     * Since version 2.16 (JUnit 4.7 provider), additional vales are available
+     * "suites"/"suitesAndClasses"/"suitesAndMethods"/"classesAndMethods"/"all".
      *
      * @since 2.2
      */
@@ -491,6 +497,75 @@ public abstract class AbstractSurefireMojo
     protected String parallel;
 
     /**
+     * (JUnit 4.7 provider) The attribute thread-count-suites allows you to specify the concurrency in test suites, i.e.:
+     * <ul>
+     *  <li>number of threads executing JUnit test suites if <code>threadCount</code> is 0 or unspecified</li>
+     *  <li>In a special case <code>threadCountSuites</code> and <code>threadCount</code> are specified
+     *      without <code>threadCountMethods</code> for <code>parallel</code>=<code>suitesAndMethods</code>.
+     *      <br/>Example1: threadCount=8 and threadCountSuites=3, the number of parallel methods is varying from 5 to 7 in your tests.
+     *      <br/>In another special case when <code>parallel</code>=<code>all</code> and the only <code>threadCountMethods</code>
+     *      is unspecified, then threads from suites and classes are reused in favor of methods.
+     *      <br/>Example2: parallel=all, threadCount=16 , threadCountSuites=2 , threadCountClasses=5,
+     *      the number of parallel methods is varying from 9 to 14 in your tests.
+     *  </li>
+     *  <li>integer number which represents the weight in ratio between
+     *      <em>threadCountSuites</em>:<code>threadCountClasses</code>:<code>threadCountMethods</code>.
+     *      As an example 2 is 20% of <code>threadCount</code> if the ratio is <em>2</em>:3:5</li>
+     *  <li>You can impose limitation on parallel suites if <code>useUnlimitedThreads</code> is specified.</li>
+     * </ul>
+     *
+     * Only makes sense to use in conjunction with the <code>parallel</code> parameter.
+     * The default value <code>0</code> behaves same as unspecified one.
+     *
+     * @since 2.16
+     */
+    @Parameter( property = "threadCountSuites", defaultValue = "0" )
+    protected int threadCountSuites;
+
+    /**
+     * (JUnit 4.7 provider) The attribute thread-count-classes allows you to specify the concurrency in test classes, i.e.:
+     * <ul>
+     *  <li>number of threads executing JUnit test classes if <code>threadCount</code> is 0 or unspecified</li>
+     *  <li>In a special case <code>threadCountClasses</code> and <code>threadCount</code> are specified
+     *      without <code>threadCountMethods</code> for <code>parallel</code>=<code>classesAndMethods</code>.
+     *      <br/>Example1: threadCount=8 and threadCountClasses=3, the number of parallel methods is varying from 5 to 7 in your tests.
+     *      <br/>In another special case when <code>parallel</code>=<code>all</code> and the only <code>threadCountMethods</code>
+     *      is unspecified, then threads from suites and classes are reused in favor of methods.
+     *      <br/>Example2: parallel=all, threadCount=16 , threadCountSuites=2 , threadCountClasses=5,
+     *      the number of parallel methods is varying from 9 to 14 in your tests.
+     *  </li>
+     *  <li>integer number which represents the weight in ratio between
+     *      <code>threadCountSuites</code>:<em>threadCountClasses</em>:<code>threadCountMethods</code>.
+     *      As an example 3 is 30% of <code>threadCount</code> if the ratio is 2:<em>3</em>:5</li>
+     *  <li>You can impose limitation on parallel classes if <code>useUnlimitedThreads</code> is specified.</li>
+     * </ul>
+     *
+     * Only makes sense to use in conjunction with the <code>parallel</code> parameter.
+     * The default value <code>0</code> behaves same as unspecified one.
+     *
+     * @since 2.16
+     */
+    @Parameter( property = "threadCountClasses", defaultValue = "0" )
+    protected int threadCountClasses;
+
+    /**
+     * (JUnit 4.7 provider) The attribute thread-count-methods allows you to specify the concurrency in test methods, i.e.:
+     * <ul>
+     *  <li>number of threads executing JUnit test methods if <code>threadCount</code> is 0 or unspecified;</li>
+     *  <li>integer number which represents the weight in ratio between <code>threadCountSuites</code>:<code>threadCountClasses</code>:<em>threadCountMethods</em>.
+     *      As an example 5 is 50% of <code>threadCount</code> if the ratio is 2:3:<em>5</em>.</li>
+     *  <li>You can impose limitation on parallel methods if <code>useUnlimitedThreads</code> is specified.</li>
+     * </ul>
+     *
+     * Only makes sense to use in conjunction with the <code>parallel</code> parameter.
+     * The default value <code>0</code> behaves same as unspecified one.
+     *
+     * @since 2.16
+     */
+    @Parameter( property = "threadCountMethods", defaultValue = "0" )
+    protected int threadCountMethods;
+
+    /**
      * Whether to trim the stack trace in the reports to just the lines within the test, or show the full trace.
      *
      * @since 2.2
@@ -1046,23 +1121,171 @@ public abstract class AbstractSurefireMojo
      * Converts old JUnit configuration parameters over to new properties based configuration
      * method. (if any are defined the old way)
      */
-    private void convertJunitCoreParameters()
+    private void convertJunitCoreParameters() throws MojoExecutionException
     {
+        checkThreadCountEntity( getThreadCountSuites(), "suites" );
+        checkThreadCountEntity( getThreadCountClasses(), "classes" );
+        checkThreadCountEntity( getThreadCountMethods(), "methods" );
+
         String usedParallel = ( getParallel() != null ) ? getParallel() : "none";
-        String usedThreadCount = ( getThreadCount() > 0 ) ? Integer.toString( getThreadCount() ) : "2";
 
+        if ( !"none".equals( usedParallel ))
+        {
+            checkNonForkedThreads( parallel );
+        }
+
+        String usedThreadCount = Integer.toString( getThreadCount() );
         getProperties().setProperty( ProviderParameterNames.PARALLEL_PROP, usedParallel );
         getProperties().setProperty( ProviderParameterNames.THREADCOUNT_PROP, usedThreadCount );
         getProperties().setProperty( "perCoreThreadCount", Boolean.toString( getPerCoreThreadCount() ) );
         getProperties().setProperty( "useUnlimitedThreads", Boolean.toString( getUseUnlimitedThreads() ) );
+        getProperties().setProperty( ProviderParameterNames.THREADCOUNTSUITES_PROP, Integer.toString( getThreadCountSuites() ) );
+        getProperties().setProperty( ProviderParameterNames.THREADCOUNTCLASSES_PROP, Integer.toString( getThreadCountClasses() ) );
+        getProperties().setProperty( ProviderParameterNames.THREADCOUNTMETHODS_PROP, Integer.toString( getThreadCountMethods() ) );
+        getProperties().setProperty( ProviderParameterNames.PARALLEL_TIMEOUT_PROP,
+                Integer.toString( getParallelTestsTimeoutInSeconds() ) );
+        getProperties().setProperty( ProviderParameterNames.PARALLEL_TIMEOUTFORCED_PROP,
+                Integer.toString( getParallelTestsTimeoutForcedInSeconds() ) );
 
         String message =
             "parallel='" + usedParallel + '\'' + ", perCoreThreadCount=" + getPerCoreThreadCount() + ", threadCount="
-                + usedThreadCount + ", useUnlimitedThreads=" + getUseUnlimitedThreads();
+                + usedThreadCount + ", useUnlimitedThreads=" + getUseUnlimitedThreads() +
+                    ", threadCountSuites=" + getThreadCountSuites() + ", threadCountClasses=" + getThreadCountClasses() +
+                    ", threadCountMethods=" + getThreadCountMethods();
 
         getLog().info( message );
     }
 
+    private void checkNonForkedThreads( String parallel ) throws MojoExecutionException
+    {
+        if ( "suites".equals( parallel ) )
+        {
+            if ( !( getUseUnlimitedThreads() || getThreadCount() > 0 ^ getThreadCountSuites() > 0 ) )
+            {
+                throw new MojoExecutionException( "Use threadCount or threadCountSuites > 0 or useUnlimitedThreads=true " +
+                        "for parallel='suites'" );
+            }
+            setThreadCountClasses( 0 );
+            setThreadCountMethods( 0 );
+        }
+        else if ( "classes".equals( parallel ) )
+        {
+            if ( !( getUseUnlimitedThreads() || getThreadCount() > 0 ^ getThreadCountClasses() > 0 ) )
+            {
+                throw new MojoExecutionException( "Use threadCount or threadCountClasses > 0 or useUnlimitedThreads=true " +
+                        "for parallel='classes'" );
+            }
+            setThreadCountSuites( 0 );
+            setThreadCountMethods( 0 );
+        }
+        else if ( "methods".equals( parallel ) )
+        {
+            if ( !( getUseUnlimitedThreads() || getThreadCount() > 0 ^ getThreadCountMethods() > 0 ) )
+            {
+                throw new MojoExecutionException( "Use threadCount or threadCountMethods > 0 or useUnlimitedThreads=true " +
+                        "for parallel='methods'" );
+            }
+            setThreadCountSuites( 0 );
+            setThreadCountClasses( 0 );
+        }
+        else if ( "suitesAndClasses".equals( parallel ) )
+        {
+            if ( !( getUseUnlimitedThreads()
+                    || onlyThreadCount()
+                    || getThreadCountSuites() > 0 && getThreadCountClasses() > 0
+                        && getThreadCount() == 0 && getThreadCountMethods() == 0
+                    || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCountClasses() > 0
+                        && getThreadCountMethods() == 0
+                    || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCount() > getThreadCountSuites()
+                        && getThreadCountClasses() == 0 && getThreadCountMethods() == 0 ) )
+            {
+                throw new MojoExecutionException( "Use useUnlimitedThreads=true, " +
+                        "or only threadCount > 0, " +
+                        "or (threadCountSuites > 0 and threadCountClasses > 0), " +
+                        "or (threadCount > 0 and threadCountSuites > 0 and threadCountClasses > 0) " +
+                        "or (threadCount > 0 and threadCountSuites > 0 and threadCount > threadCountSuites) " +
+                        "for parallel='suitesAndClasses' or 'both'" );
+            }
+            setThreadCountMethods( 0 );
+        }
+        else if ( "suitesAndMethods".equals( parallel ) )
+        {
+            if ( !( getUseUnlimitedThreads()
+                    || onlyThreadCount()
+                    || getThreadCountSuites() > 0 && getThreadCountMethods() > 0
+                        && getThreadCount() == 0 && getThreadCountClasses() == 0
+                    || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCountMethods() > 0
+                        && getThreadCountClasses() == 0
+                    || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCount() > getThreadCountSuites()
+                        && getThreadCountClasses() == 0 && getThreadCountMethods() == 0 ) )
+            {
+                throw new MojoExecutionException( "Use useUnlimitedThreads=true, " +
+                        "or only threadCount > 0, " +
+                        "or (threadCountSuites > 0 and threadCountMethods > 0), " +
+                        "or (threadCount > 0 and threadCountSuites > 0 and threadCountMethods > 0), " +
+                        "or (threadCount > 0 and threadCountSuites > 0 and threadCount > threadCountSuites) " +
+                        "for parallel='suitesAndMethods'" );
+            }
+            setThreadCountClasses( 0 );
+        }
+        else if ( "both".equals( parallel ) || "classesAndMethods".equals( parallel ) )
+        {
+            if ( !( getUseUnlimitedThreads()
+                    || onlyThreadCount()
+                    || getThreadCountClasses() > 0 && getThreadCountMethods() > 0
+                        && getThreadCount() == 0 && getThreadCountSuites() == 0
+                    || getThreadCount() > 0 && getThreadCountClasses() > 0 && getThreadCountMethods() > 0
+                        && getThreadCountSuites() == 0
+                    || getThreadCount() > 0 && getThreadCountClasses() > 0 && getThreadCount() > getThreadCountClasses()
+                        && getThreadCountSuites() == 0 && getThreadCountMethods() == 0 ) )
+            {
+                throw new MojoExecutionException( "Use useUnlimitedThreads=true, " +
+                        "or only threadCount > 0, " +
+                        "or (threadCountClasses > 0 and threadCountMethods > 0), " +
+                        "or (threadCount > 0 and threadCountClasses > 0 and threadCountMethods > 0), " +
+                        "or (threadCount > 0 and threadCountClasses > 0 and threadCount > threadCountClasses) " +
+                        "for parallel='both' or parallel='classesAndMethods'" );
+            }
+            setThreadCountSuites( 0 );
+        }
+        else if ( "all".equals( parallel ) )
+        {
+            if ( !( getUseUnlimitedThreads()
+                    || onlyThreadCount()
+                    || getThreadCountSuites() > 0 && getThreadCountClasses() > 0 && getThreadCountMethods() > 0
+                    || getThreadCount() > 0 && getThreadCountSuites() > 0 && getThreadCountClasses() > 0
+                        && getThreadCountMethods() == 0
+                        && getThreadCount() > ( getThreadCountSuites() + getThreadCountClasses() ) ) )
+            {
+                throw new MojoExecutionException( "Use useUnlimitedThreads=true, " +
+                        "or only threadCount > 0, " +
+                        "or (threadCountSuites > 0 and threadCountClasses > 0 and threadCountMethods > 0), " +
+                        "or every thread-count is specified, " +
+                        "or (threadCount > 0 and threadCountSuites > 0 and threadCountClasses > 0 " +
+                            "and threadCount > threadCountSuites + threadCountClasses) " +
+                        "for parallel='all'" );
+            }
+        }
+        else
+        {
+            throw new MojoExecutionException( "Illegal parallel='" + parallel + "'" );
+        }
+    }
+
+    private boolean onlyThreadCount()
+    {
+        return getThreadCount() > 0 && getThreadCountSuites() == 0 && getThreadCountClasses() == 0
+                && getThreadCountMethods() == 0;
+    }
+
+    private static void checkThreadCountEntity(int count, String entity) throws MojoExecutionException
+    {
+        if ( count < 0 )
+        {
+            throw new MojoExecutionException("parallel maven execution does not allow negative thread-count" + entity);
+        }
+    }
+
     private boolean isJunit47Compatible( Artifact artifact )
     {
         return dependencyResolver.isWithinVersionSpec( artifact, "[4.7,)" );
@@ -1668,6 +1891,8 @@ public abstract class AbstractSurefireMojo
         checksum.add( getArgLine() );
         checksum.add( getDebugForkedProcess() );
         checksum.add( getForkedProcessTimeoutInSeconds() );
+        checksum.add( getParallelTestsTimeoutInSeconds() );
+        checksum.add( getParallelTestsTimeoutForcedInSeconds() );
         checksum.add( getEnvironmentVariables() );
         checksum.add( getWorkingDirectory() );
         checksum.add( isChildDelegation() );
@@ -1677,6 +1902,9 @@ public abstract class AbstractSurefireMojo
         checksum.add( getJunitArtifact() );
         checksum.add( getTestNGArtifactName() );
         checksum.add( getThreadCount() );
+        checksum.add( getThreadCountSuites() );
+        checksum.add( getThreadCountClasses() );
+        checksum.add( getThreadCountMethods() );
         checksum.add( getPerCoreThreadCount() );
         checksum.add( getUseUnlimitedThreads() );
         checksum.add( getParallel() );
@@ -2054,7 +2282,7 @@ public abstract class AbstractSurefireMojo
             return true;
         }
 
-        public void addProviderProperties()
+        public void addProviderProperties() throws MojoExecutionException
         {
         }
 
@@ -2093,7 +2321,7 @@ public abstract class AbstractSurefireMojo
             return junitDepArtifact != null || isAnyJunit4( junitArtifact );
         }
 
-        public void addProviderProperties()
+        public void addProviderProperties() throws MojoExecutionException
         {
         }
 
@@ -2137,7 +2365,7 @@ public abstract class AbstractSurefireMojo
             return isAny47ProvidersForcers && ( isJunitArtifact47 || is47CompatibleJunitDep() );
         }
 
-        public void addProviderProperties()
+        public void addProviderProperties() throws MojoExecutionException
         {
             convertJunitCoreParameters();
             convertGroupParameters();
@@ -2494,6 +2722,39 @@ public abstract class AbstractSurefireMojo
         this.parallel = parallel;
     }
 
+    public int getThreadCountSuites()
+    {
+        return threadCountSuites;
+    }
+
+    @SuppressWarnings( "UnusedDeclaration" )
+    public void setThreadCountSuites( int threadCountSuites )
+    {
+        this.threadCountSuites = threadCountSuites;
+    }
+
+    public int getThreadCountClasses()
+    {
+        return threadCountClasses;
+    }
+
+    @SuppressWarnings( "UnusedDeclaration" )
+    public void setThreadCountClasses( int threadCountClasses )
+    {
+        this.threadCountClasses = threadCountClasses;
+    }
+
+    public int getThreadCountMethods()
+    {
+        return threadCountMethods;
+    }
+
+    @SuppressWarnings( "UnusedDeclaration" )
+    public void setThreadCountMethods( int threadCountMethods )
+    {
+        this.threadCountMethods = threadCountMethods;
+    }
+
     public boolean isTrimStackTrace()
     {
         return trimStackTrace;

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java
----------------------------------------------------------------------
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java
index 44c7d19..0b5a1af 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java
@@ -101,6 +101,14 @@ public interface SurefireExecutionParameters
 
     void setForkedProcessTimeoutInSeconds( int forkedProcessTimeoutInSeconds );
 
+    int getParallelTestsTimeoutInSeconds();
+
+    void setParallelTestsTimeoutInSeconds( int parallelTestsTimeoutInSeconds );
+
+    int getParallelTestsTimeoutForcedInSeconds();
+
+    void setParallelTestsTimeoutForcedInSeconds( int parallelTestsTimeoutForcedInSeconds );
+
     boolean isUseSystemClassLoader();
 
     void setUseSystemClassLoader( boolean useSystemClassLoader );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
----------------------------------------------------------------------
diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
index f0ac255..189161b 100644
--- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
+++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java
@@ -119,6 +119,27 @@ public class SurefirePlugin
      */
     @Parameter( property = "surefire.timeout" )
     private int forkedProcessTimeoutInSeconds;
+
+    /**
+     * Stop executing queued parallel JUnit tests after a certain number of seconds.
+     * If set to 0, wait forever, never timing out.
+     * Makes sense with specified <code>parallel</code> different from "none".
+     *
+     * @since 2.16
+     */
+    @Parameter( property = "surefire.parallel.timeout" )
+    private int parallelTestsTimeoutInSeconds;
+
+    /**
+     * Stop executing queued parallel JUnit tests
+     * and <em>interrupt</em> currently running tests after a certain number of seconds.
+     * If set to 0, wait forever, never timing out.
+     * Makes sense with specified <code>parallel</code> different from "none".
+     *
+     * @since 2.16
+     */
+    @Parameter( property = "surefire.parallel.forcedTimeout" )
+    private int parallelTestsTimeoutForcedInSeconds;
     
     /**
      * A list of &lt;include> elements specifying the tests (by pattern) that should be included in testing. When not
@@ -426,6 +447,22 @@ public class SurefirePlugin
         this.forkedProcessTimeoutInSeconds = forkedProcessTimeoutInSeconds;
     }
 
+    public int getParallelTestsTimeoutInSeconds() {
+        return parallelTestsTimeoutInSeconds;
+    }
+
+    public void setParallelTestsTimeoutInSeconds( int parallelTestsTimeoutInSeconds ) {
+        this.parallelTestsTimeoutInSeconds = parallelTestsTimeoutInSeconds;
+    }
+
+    public int getParallelTestsTimeoutForcedInSeconds() {
+        return parallelTestsTimeoutForcedInSeconds;
+    }
+
+    public void setParallelTestsTimeoutForcedInSeconds( int parallelTestsTimeoutForcedInSeconds ) {
+        this.parallelTestsTimeoutForcedInSeconds = parallelTestsTimeoutForcedInSeconds;
+    }
+
     public void setTest( String test )
     {
         this.test = test;

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
index a5e7daf..dee42aa 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java
@@ -92,7 +92,7 @@ public class BaseProviderFactory
     private int getThreadCount()
     {
         final String threadcount = (String) providerProperties.get( ProviderParameterNames.THREADCOUNT_PROP );
-        return threadcount == null ? 1 : Integer.parseInt( threadcount );
+        return threadcount == null ? 1 : Math.max( Integer.parseInt( threadcount ), 1 );
     }
 
     public RunOrderCalculator getRunOrderCalculator()

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderParameterNames.java
----------------------------------------------------------------------
diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderParameterNames.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderParameterNames.java
index b70fbc9..72c16ac 100644
--- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderParameterNames.java
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ProviderParameterNames.java
@@ -31,4 +31,14 @@ public class ProviderParameterNames
 
     public static final String PARALLEL_PROP = "parallel";
 
+    public static final String THREADCOUNTSUITES_PROP = "threadcountsuites";
+
+    public static final String THREADCOUNTCLASSES_PROP = "threadcountclasses";
+
+    public static final String THREADCOUNTMETHODS_PROP = "threadcountmethods";
+
+    public static final String PARALLEL_TIMEOUT_PROP = "paralleltimeout";
+
+    public static final String PARALLEL_TIMEOUTFORCED_PROP = "paralleltimeoutforced";
+
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Junit47WithCucumberIT.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Junit47WithCucumberIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Junit47WithCucumberIT.java
index 08f6f6c..09e857a 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Junit47WithCucumberIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/Junit47WithCucumberIT.java
@@ -60,7 +60,10 @@ public class Junit47WithCucumberIT
 
     private void doTest( String parallel, int total )
     {
-        unpack( "junit47-cucumber" ).sysProp( "parallel", parallel ).executeTest().assertTestSuiteResults( total, 0, 2,
-                                                                                                           0 );
+        unpack( "junit47-cucumber" )
+                .sysProp( "parallel", parallel )
+                .sysProp( "threadCount", "2" )
+                .executeTest()
+                .assertTestSuiteResults( total, 0, 2, 0 );
     }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java
index d96f480..39d2ab9 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/TestSingleMethodIT.java
@@ -54,8 +54,10 @@ public class TestSingleMethodIT
     public void testJunit48parallel()
         throws Exception
     {
-        unpack( "junit48-single-method" ).parallelClasses().executeTest().verifyErrorFreeLog().assertTestSuiteResults(
-            1, 0, 0, 0 );
+        unpack( "junit48-single-method" )
+                .executeTest()
+                .verifyErrorFreeLog()
+                .assertTestSuiteResults( 1, 0, 0, 0 );
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
index fffde85..7b65783 100755
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/fixture/SurefireLauncher.java
@@ -321,6 +321,11 @@ public class SurefireLauncher
     }
 
 
+    public SurefireLauncher parallelSuites()
+    {
+        return parallel( "suites" );
+    }
+
     public SurefireLauncher parallelClasses()
     {
         return parallel( "classes" );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
index fe72087..32aedbf 100644
--- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
+++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire943ReportContentIT.java
@@ -49,8 +49,13 @@ public class Surefire943ReportContentIT
     private void doTest( String parallelMode )
         throws Exception
     {
-        OutputValidator validator =
-            unpack( "surefire-943-report-content" ).maven().sysProp( "parallel", parallelMode ).withFailure().executeTest();
+        OutputValidator validator = unpack( "surefire-943-report-content" )
+                .maven()
+                .sysProp( "parallel", parallelMode )
+                .sysProp( "threadCount", 4 )
+                .withFailure()
+                .executeTest();
+
         validator.assertTestSuiteResults( 9, 0, 3, 3 );
 
         validate( validator, "org.sample.module.My1Test", 1 );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-integration-tests/src/test/resources/junit44-dep/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/junit44-dep/pom.xml b/surefire-integration-tests/src/test/resources/junit44-dep/pom.xml
index 7f77872..6e6d476 100644
--- a/surefire-integration-tests/src/test/resources/junit44-dep/pom.xml
+++ b/surefire-integration-tests/src/test/resources/junit44-dep/pom.xml
@@ -66,6 +66,7 @@
         <version>${surefire.version}</version>
         <configuration>
           <parallel>classes</parallel>
+          <threadCount>1</threadCount>
         </configuration>
       </plugin>
     </plugins>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-integration-tests/src/test/resources/junit44-dep/src/test/java/junit44Dep/BasicTest.java
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/junit44-dep/src/test/java/junit44Dep/BasicTest.java b/surefire-integration-tests/src/test/resources/junit44-dep/src/test/java/junit44Dep/BasicTest.java
index 3db5d19..219b8da 100644
--- a/surefire-integration-tests/src/test/resources/junit44-dep/src/test/java/junit44Dep/BasicTest.java
+++ b/surefire-integration-tests/src/test/resources/junit44-dep/src/test/java/junit44Dep/BasicTest.java
@@ -54,6 +54,7 @@ public class BasicTest
     public void testSetUp()
     {
         Assert.assertTrue( "setUp was not called", setUpCalled );
+        Assert.assertFalse( "tearDown was called", tearDownCalled );
         Assert.assertThat( true, Is.is( true ) );
     }
 

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreParameters.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreParameters.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreParameters.java
index a2dcea2..157ed58 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreParameters.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreParameters.java
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.junitcore;
  * under the License.
  */
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Properties;
 import org.apache.maven.surefire.booter.ProviderParameterNames;
 
@@ -33,6 +35,16 @@ class JUnitCoreParameters
 
     private final int threadCount;
 
+    private final int threadCountSuites;
+
+    private final int threadCountClasses;
+
+    private final int threadCountMethods;
+
+    private final int parallelTestsTimeoutInSeconds;
+
+    private final int parallelTestsTimeoutForcedInSeconds;
+
     private final Boolean useUnlimitedThreads;
 
     public static final String PARALLEL_KEY = ProviderParameterNames.PARALLEL_PROP;
@@ -41,29 +53,70 @@ class JUnitCoreParameters
 
     public static final String THREADCOUNT_KEY = ProviderParameterNames.THREADCOUNT_PROP;
 
+    public static final String THREADCOUNTSUITES_KEY = ProviderParameterNames.THREADCOUNTSUITES_PROP;
+
+    public static final String THREADCOUNTCLASSES_KEY = ProviderParameterNames.THREADCOUNTCLASSES_PROP;
+
+    public static final String THREADCOUNTMETHODS_KEY = ProviderParameterNames.THREADCOUNTMETHODS_PROP;
+
     public static final String USEUNLIMITEDTHREADS_KEY = "useUnlimitedThreads";
 
+    public static final String PARALLEL_TIMEOUT_KEY = ProviderParameterNames.PARALLEL_TIMEOUT_PROP;
+
+    public static final String PARALLEL_TIMEOUTFORCED_KEY = ProviderParameterNames.PARALLEL_TIMEOUTFORCED_PROP;
+
     public JUnitCoreParameters( Properties properties )
     {
-        this.parallel = properties.getProperty( PARALLEL_KEY, "none" ).toLowerCase();
-        this.perCoreThreadCount = Boolean.valueOf( properties.getProperty( PERCORETHREADCOUNT_KEY, "true" ) );
-        this.threadCount = Integer.valueOf( properties.getProperty( THREADCOUNT_KEY, "2" ) );
-        this.useUnlimitedThreads = Boolean.valueOf( properties.getProperty( USEUNLIMITEDTHREADS_KEY, "false" ) );
+        parallel = properties.getProperty( PARALLEL_KEY, "none" ).toLowerCase();
+        perCoreThreadCount = Boolean.valueOf( properties.getProperty( PERCORETHREADCOUNT_KEY, "true" ) );
+        threadCount = Integer.valueOf( properties.getProperty( THREADCOUNT_KEY, "0" ) );
+        threadCountMethods = Integer.valueOf( properties.getProperty( THREADCOUNTMETHODS_KEY, "0" ) );
+        threadCountClasses = Integer.valueOf( properties.getProperty( THREADCOUNTCLASSES_KEY, "0" ) );
+        threadCountSuites = Integer.valueOf( properties.getProperty( THREADCOUNTSUITES_KEY, "0" ) );
+        useUnlimitedThreads = Boolean.valueOf( properties.getProperty( USEUNLIMITEDTHREADS_KEY, "false" ) );
+        parallelTestsTimeoutInSeconds = Integer.valueOf( properties.getProperty( PARALLEL_TIMEOUT_KEY, "0" ) );
+        parallelTestsTimeoutForcedInSeconds = Integer.valueOf( properties.getProperty( PARALLEL_TIMEOUTFORCED_KEY, "0" ) );
+    }
+
+    private static Collection<String> lowerCase( String... elements )
+    {
+        ArrayList<String> lowerCase = new ArrayList<String>();
+        for ( String element : elements )
+        {
+            lowerCase.add( element.toLowerCase() );
+        }
+        return lowerCase;
+    }
+
+    private boolean isAllParallel()
+    {
+        return "all".equals( parallel );
     }
 
     public boolean isParallelMethod()
     {
-        return "methods".equals( parallel );
+        return isAllParallel()
+                || lowerCase( "both", "methods", "suitesAndMethods", "classesAndMethods" ).contains( parallel );
     }
 
     public boolean isParallelClasses()
     {
-        return "classes".equals( parallel );
+        return isAllParallel()
+                || lowerCase( "both", "classes", "suitesAndClasses", "classesAndMethods" ).contains( parallel );
+    }
+
+    public boolean isParallelSuites()
+    {
+        return isAllParallel() || lowerCase( "suites", "suitesAndClasses", "suitesAndMethods" ).contains( parallel );
     }
 
+    /**
+     * @deprecated Instead use the expression ( {@link #isParallelMethod()} && {@link #isParallelClasses()} ).
+     */
+    @Deprecated
     public boolean isParallelBoth()
     {
-        return "both".equals( parallel );
+        return isParallelMethod() && isParallelClasses();
     }
 
     public Boolean isPerCoreThreadCount()
@@ -76,25 +129,51 @@ class JUnitCoreParameters
         return threadCount;
     }
 
+    public int getThreadCountMethods()
+    {
+        return threadCountMethods;
+    }
+
+    public int getThreadCountClasses()
+    {
+        return threadCountClasses;
+    }
+
+    public int getThreadCountSuites()
+    {
+        return threadCountSuites;
+    }
+
     public Boolean isUseUnlimitedThreads()
     {
         return useUnlimitedThreads;
     }
 
+    public int getParallelTestsTimeoutInSeconds()
+    {
+        return parallelTestsTimeoutInSeconds;
+    }
+
+    public int getParallelTestsTimeoutForcedInSeconds()
+    {
+        return parallelTestsTimeoutForcedInSeconds;
+    }
+
     public boolean isNoThreading()
     {
-        return !( isParallelClasses() || isParallelMethod() || isParallelBoth() );
+        return !isAnyParallelitySelected();
     }
 
     public boolean isAnyParallelitySelected()
     {
-        return !isNoThreading();
+        return isParallelSuites() || isParallelClasses() || isParallelMethod();
     }
 
     @Override
     public String toString()
     {
         return "parallel='" + parallel + '\'' + ", perCoreThreadCount=" + perCoreThreadCount + ", threadCount="
-            + threadCount + ", useUnlimitedThreads=" + useUnlimitedThreads;
+            + threadCount + ", useUnlimitedThreads=" + useUnlimitedThreads + ", threadCountSuites=" + threadCountSuites
+                + ", threadCountClasses=" + threadCountClasses + ", threadCountMethods=" + threadCountMethods;
     }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
index 14af56c..de0673c 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java
@@ -100,8 +100,7 @@ public class JUnitCoreProvider
 
     private boolean isSingleThreaded()
     {
-        return !jUnitCoreParameters.isAnyParallelitySelected()
-            || ( testsToRun.containsExactly( 1 ) && !jUnitCoreParameters.isParallelMethod() );
+        return jUnitCoreParameters.isNoThreading();
     }
 
     public RunResult invoke( Object forkTestSet )
@@ -153,8 +152,8 @@ public class JUnitCoreProvider
 
             RunListener listener =
                 ConcurrentRunListener.createInstance( testSetMap, reporterFactory,
-                                                      jUnitCoreParameters.isParallelClasses(),
-                                                      jUnitCoreParameters.isParallelBoth(), consoleLogger );
+                                                      isParallelTypes(),
+                                                      isParallelMethodsAndTypes(), consoleLogger );
             ConsoleOutputCapture.startCapture( (ConsoleOutputReceiver) listener );
 
             jUnit4RunListener = new JUnitCoreRunListener( listener, testSetMap );
@@ -162,6 +161,16 @@ public class JUnitCoreProvider
         return jUnit4RunListener;
     }
 
+    private boolean isParallelMethodsAndTypes()
+    {
+        return jUnitCoreParameters.isParallelMethod() && isParallelTypes();
+    }
+
+    private boolean isParallelTypes()
+    {
+        return jUnitCoreParameters.isParallelClasses() || jUnitCoreParameters.isParallelSuites();
+    }
+
     private Filter createJUnit48Filter()
     {
         final FilterFactory filterFactory = new FilterFactory( testClassLoader );

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
index 4473063..45cf34c 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreWrapper.java
@@ -19,14 +19,20 @@ package org.apache.maven.surefire.junitcore;
  * under the License.
  */
 
-import java.util.Iterator;
+import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
+import java.util.TreeSet;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
 import org.apache.maven.surefire.common.junit4.JUnit4RunListener;
+import org.apache.maven.surefire.junitcore.pc.ParallelComputer;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.apache.maven.surefire.util.TestsToRun;
 
 import org.junit.runner.Computer;
+import org.junit.runner.Description;
 import org.junit.runner.JUnitCore;
 import org.junit.runner.Request;
 import org.junit.runner.Result;
@@ -73,24 +79,21 @@ class JUnitCoreWrapper
                                 List<RunListener> listeners, Filter filter )
         throws TestSetFailedException
     {
-        Computer computer = getComputer( jUnitCoreParameters );
-
+        ComputerWrapper computerWrapper = createComputer( jUnitCoreParameters );
         JUnitCore junitCore = createJUnitCore( listeners );
-
-        try
+        if ( testsToRun.allowEagerReading() )
         {
-            if ( testsToRun.allowEagerReading() )
-            {
-                executeEager( testsToRun, filter, computer, junitCore );
-            }
-            else
-            {
-                exeuteLazy( testsToRun, filter, computer, junitCore );
-            }
+            executeEager( testsToRun, filter, computerWrapper.getComputer(), junitCore );
         }
-        finally
+        else
+        {
+            exeuteLazy( testsToRun, filter, computerWrapper.getComputer(), junitCore );
+        }
+
+        String timeoutMessage = computerWrapper.describeElapsedTimeout();
+        if ( timeoutMessage.length() != 0 )
         {
-            closeIfConfigurable( computer );
+            throw new TestSetFailedException( timeoutMessage );
         }
     }
 
@@ -108,21 +111,20 @@ class JUnitCoreWrapper
             throws TestSetFailedException 
     {
         Class[] tests = testsToRun.getLocatedClasses();
-        createReqestAndRun( filter, computer, junitCore, tests );
+        createRequestAndRun( filter, computer, junitCore, tests );
     }
 
     private static void exeuteLazy(TestsToRun testsToRun, Filter filter, Computer computer, JUnitCore junitCore)
             throws TestSetFailedException
     {
         // in order to support LazyTestsToRun, the iterator must be used
-        Iterator<?> classIter = testsToRun.iterator();
-        while ( classIter.hasNext() )
+        for ( Class clazz : testsToRun )
         {
-            createReqestAndRun( filter, computer, junitCore, new Class[]{ (Class<?>) classIter.next() } );
+            createRequestAndRun( filter, computer, junitCore, clazz );
         }
     }
 
-    private static void createReqestAndRun( Filter filter, Computer computer, JUnitCore junitCore, Class<?>[] classesToRun )
+    private static void createRequestAndRun( Filter filter, Computer computer, JUnitCore junitCore, Class<?>... classesToRun )
             throws TestSetFailedException
     {
         Request req = Request.classes( computer, classesToRun );
@@ -140,46 +142,110 @@ class JUnitCoreWrapper
         JUnit4RunListener.rethrowAnyTestMechanismFailures( run );
     }
 
-    private static void closeIfConfigurable( Computer computer )
+    private static ComputerWrapper createComputer( JUnitCoreParameters parameters )
         throws TestSetFailedException
     {
-        if ( computer instanceof ConfigurableParallelComputer )
-        {
-            try
-            {
-                ( (ConfigurableParallelComputer) computer ).close();
-            }
-            catch ( ExecutionException e )
-            {
-                throw new TestSetFailedException( e );
-            }
-        }
+        return parameters.isNoThreading() ? new ComputerWrapper( Computer.serial() ) : createParallelComputer( parameters );
     }
 
-    private static Computer getComputer( JUnitCoreParameters jUnitCoreParameters )
-        throws TestSetFailedException
+    private static ComputerWrapper createParallelComputer( JUnitCoreParameters parameters )
+            throws TestSetFailedException
     {
-        if ( jUnitCoreParameters.isNoThreading() )
-        {
-            return new Computer();
-        }
-        return getConfigurableParallelComputer( jUnitCoreParameters );
+        ParallelComputer pc = ParallelComputerFactory.createParallelComputer( parameters );
+
+        int timeout = parameters.getParallelTestsTimeoutInSeconds();
+
+        int timeoutForced = parameters.getParallelTestsTimeoutForcedInSeconds();
+
+        Future<Collection<Description>> testsBeforeShutdown =
+                timeout > 0 ? pc.scheduleShutdown( timeout, TimeUnit.SECONDS ) : null;
+
+        Future<Collection<Description>> testsBeforeForcedShutdown =
+                timeoutForced > 0 ? pc.scheduleForcedShutdown( timeoutForced, TimeUnit.SECONDS ) : null;
+
+        return new ComputerWrapper( pc, timeout, testsBeforeShutdown, timeoutForced, testsBeforeForcedShutdown );
     }
 
-    private static Computer getConfigurableParallelComputer( JUnitCoreParameters jUnitCoreParameters )
-        throws TestSetFailedException
+    private static class ComputerWrapper
     {
-        if ( jUnitCoreParameters.isUseUnlimitedThreads() )
+        private final Computer computer;
+        private final int timeout;
+        private final int timeoutForced;
+        private final Future<Collection<Description>> testsBeforeShutdown;
+        private final Future<Collection<Description>> testsBeforeForcedShutdown;
+
+        ComputerWrapper( Computer computer )
         {
-            return new ConfigurableParallelComputer();
+            this( computer, 0, null, 0, null );
         }
-        else
+
+        ComputerWrapper( Computer computer,
+                         int timeout, Future<Collection<Description>> testsBeforeShutdown,
+                         int timeoutForced, Future<Collection<Description>> testsBeforeForcedShutdown )
         {
-            return new ConfigurableParallelComputer(
-                jUnitCoreParameters.isParallelClasses() | jUnitCoreParameters.isParallelBoth(),
-                jUnitCoreParameters.isParallelMethod() | jUnitCoreParameters.isParallelBoth(),
-                jUnitCoreParameters.getThreadCount(), jUnitCoreParameters.isPerCoreThreadCount() );
+            this.computer = computer;
+            this.timeout = timeout;
+            this.testsBeforeShutdown = testsBeforeShutdown;
+            this.timeoutForced = timeoutForced;
+            this.testsBeforeForcedShutdown = testsBeforeForcedShutdown;
         }
-    }
 
+        Computer getComputer()
+        {
+            return computer;
+        }
+
+        String describeElapsedTimeout() throws TestSetFailedException
+        {
+            TreeSet<String> executedTests = new TreeSet<String>();
+            if ( timeout > 0 )
+            {
+                executedTests.addAll( printShutdownHook( testsBeforeShutdown ) );
+            }
+
+            if ( timeoutForced > 0 )
+            {
+                executedTests.addAll( printShutdownHook( testsBeforeForcedShutdown ) );
+            }
+
+            StringBuilder msg = new StringBuilder();
+            if ( !executedTests.isEmpty() )
+            {
+                msg.append( "The test run has finished abruptly after timeout of " );
+                msg.append( Math.min( timeout, timeoutForced ) );
+                msg.append( " seconds.\n" );
+                msg.append( "These tests were executed in prior of the shutdown operation:\n" );
+                for ( String executedTest : executedTests )
+                {
+                    msg.append( executedTest ).append( "\n" );
+                }
+            }
+            return msg.toString();
+        }
+
+        static Collection<String> printShutdownHook( Future<Collection<Description>> future )
+                throws TestSetFailedException
+        {
+            if ( !future.isCancelled() && future.isDone() )
+            {
+                try
+                {
+                    TreeSet<String> executedTests = new TreeSet<String>();
+                    for ( Description executedTest : future.get() )
+                    {
+                        if ( executedTest != null && executedTest.getDisplayName() != null )
+                        {
+                            executedTests.add( executedTest.getDisplayName() );
+                        }
+                    }
+                    return executedTests;
+                }
+                catch ( Exception e )
+                {
+                    throw new TestSetFailedException( e );
+                }
+            }
+            return Collections.emptySet();
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ParallelComputerFactory.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ParallelComputerFactory.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ParallelComputerFactory.java
new file mode 100644
index 0000000..3937cd4
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/ParallelComputerFactory.java
@@ -0,0 +1,368 @@
+package org.apache.maven.surefire.junitcore;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.junitcore.pc.ParallelComputer;
+import org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilder;
+import org.apache.maven.surefire.testset.TestSetFailedException;
+
+/**
+ * An algorithm which configures {@link ParallelComputer} with allocated thread resources by given {@link JUnitCoreParameters}.
+ * The <code>AbstractSurefireMojo</code> has to provide correct combinations of thread-counts and <em>parallel</em>.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see org.apache.maven.surefire.junitcore.pc.ParallelComputerBuilder
+ */
+final class ParallelComputerFactory
+{
+    private static int availableProcessors = Runtime.getRuntime().availableProcessors();
+
+    static class Concurrency
+    {
+        int suites, classes, methods, capacity;
+    }
+
+    private ParallelComputerFactory()
+    {
+        throw new IllegalStateException("Suppresses calling constructor, ensuring non-instantiability.");
+    }
+
+    /*
+    * For testing purposes.
+    */
+    static void overrideAvailableProcessors( int availableProcessors )
+    {
+        ParallelComputerFactory.availableProcessors = availableProcessors;
+    }
+
+    /*
+    * For testing purposes.
+    */
+    static void setDefaultAvailableProcessors()
+    {
+        ParallelComputerFactory.availableProcessors = Runtime.getRuntime().availableProcessors();
+    }
+
+    static ParallelComputer createParallelComputer( JUnitCoreParameters params ) throws TestSetFailedException
+    {
+        Concurrency concurrency = resolveConcurrency( params );
+        ParallelComputerBuilder builder = new ParallelComputerBuilder();
+
+        if ( params.isParallelSuites() )
+        {
+            resolveSuitesConcurrency( builder, concurrency.suites );
+        }
+
+        if ( params.isParallelClasses() )
+        {
+            resolveClassesConcurrency( builder, concurrency.classes );
+        }
+
+        if ( params.isParallelMethod() )
+        {
+            resolveMethodsConcurrency( builder, concurrency.methods );
+        }
+
+        resolveCapacity( builder, concurrency.capacity );
+        return builder.buildComputer();
+    }
+
+    static Concurrency resolveConcurrency( JUnitCoreParameters params ) throws TestSetFailedException
+    {
+        if ( !params.isAnyParallelitySelected() )
+        {
+            throw new TestSetFailedException( "Unspecified parameter '" + JUnitCoreParameters.PARALLEL_KEY + "'." );
+        }
+
+        if ( !params.isUseUnlimitedThreads() && !hasThreadCount( params ) && !hasThreadCounts( params ) )
+        {
+            throw new TestSetFailedException( "Unspecified thread-count(s). " +
+                    "See the parameters " + JUnitCoreParameters.USEUNLIMITEDTHREADS_KEY + ", "
+                    + JUnitCoreParameters.THREADCOUNT_KEY + ", " + JUnitCoreParameters.THREADCOUNTSUITES_KEY + ", "
+                    + JUnitCoreParameters.THREADCOUNTCLASSES_KEY + ", " + JUnitCoreParameters.THREADCOUNTMETHODS_KEY + ".");
+        }
+
+        if ( params.isUseUnlimitedThreads() )
+        {
+            return concurrencyForUnlimitedThreads( params );
+        }
+        else
+        {
+            if ( hasThreadCount( params ) )
+            {
+                if ( hasThreadCounts( params ) )
+                {
+                    return isLeafUnspecified( params ) ?
+                            concurrencyFromAllThreadCountsButUnspecifiedLeafCount( params ) :
+                            concurrencyFromAllThreadCounts( params );
+                }
+                else
+                {
+                    return estimateConcurrency( params );
+                }
+            }
+            else
+            {
+                return concurrencyFromThreadCounts( params );
+            }
+        }
+    }
+
+    private static void resolveSuitesConcurrency( ParallelComputerBuilder builder, int concurrency )
+    {
+        if ( concurrency > 0 )
+        {
+            if ( concurrency == Integer.MAX_VALUE )
+            {
+                builder.parallelSuites();
+            }
+            else
+            {
+                builder.parallelSuites( concurrency );
+            }
+        }
+    }
+
+    private static void resolveClassesConcurrency( ParallelComputerBuilder builder, int concurrency )
+    {
+        if ( concurrency > 0 )
+        {
+            if ( concurrency == Integer.MAX_VALUE )
+            {
+                builder.parallelClasses();
+            }
+            else
+            {
+                builder.parallelClasses( concurrency );
+            }
+        }
+    }
+
+    private static void resolveMethodsConcurrency( ParallelComputerBuilder builder, int concurrency )
+    {
+        if ( concurrency > 0 )
+        {
+            if ( concurrency == Integer.MAX_VALUE )
+            {
+                builder.parallelMethods();
+            }
+            else
+            {
+                builder.parallelMethods( concurrency );
+            }
+        }
+    }
+
+    private static void resolveCapacity( ParallelComputerBuilder builder, int capacity )
+    {
+        if ( capacity > 0 )
+        {
+            builder.useOnePool( capacity );
+        }
+    }
+
+    private static Concurrency concurrencyForUnlimitedThreads( JUnitCoreParameters params )
+    {
+        Concurrency concurrency = new Concurrency();
+        concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
+        concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
+        concurrency.methods = params.isParallelMethod() ? threadCountMethods( params ) : 0;
+        concurrency.capacity = Integer.MAX_VALUE;
+        return concurrency;
+    }
+
+    private static Concurrency estimateConcurrency( JUnitCoreParameters params )
+    {
+        Concurrency concurrency = new Concurrency();
+        concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
+        concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
+        concurrency.methods = params.isParallelMethod() ? params.getThreadCountMethods() : 0;
+        concurrency.capacity = params.getThreadCount();
+
+        // estimate parallel thread counts
+        double ratio = 1d / countParallelEntities( params );
+        int threads = multiplyByCoreCount( params, ratio * concurrency.capacity );
+        concurrency.suites = params.isParallelSuites() ? threads : 0;
+        concurrency.classes = params.isParallelClasses() ? threads : 0;
+        concurrency.methods = params.isParallelMethod() ? threads : 0;
+        if ( countParallelEntities( params ) == 1 )
+        {
+            concurrency.capacity = 0;
+        }
+        else
+        {
+            concurrency.capacity = multiplyByCoreCount( params, concurrency.capacity );
+            adjustLeaf( params, concurrency );
+        }
+        return concurrency;
+    }
+
+    private static Concurrency concurrencyFromAllThreadCountsButUnspecifiedLeafCount( JUnitCoreParameters params )
+    {
+        Concurrency concurrency = new Concurrency();
+        concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
+        concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
+        concurrency.methods = params.isParallelMethod() ? params.getThreadCountMethods() : 0;
+        concurrency.capacity = params.getThreadCount();
+
+        setLeafInfinite( params, concurrency );
+        concurrency.suites = params.isParallelSuites() ? multiplyByCoreCount( params, concurrency.suites ) : 0;
+        concurrency.classes = params.isParallelClasses() ? multiplyByCoreCount( params, concurrency.classes ) : 0;
+        concurrency.methods = params.isParallelMethod() ? multiplyByCoreCount( params, concurrency.methods ) : 0;
+        concurrency.capacity = multiplyByCoreCount( params, concurrency.capacity );
+
+        return concurrency;
+    }
+
+    private static Concurrency concurrencyFromAllThreadCounts( JUnitCoreParameters params )
+    {
+        Concurrency concurrency = new Concurrency();
+        concurrency.suites = params.isParallelSuites() ? params.getThreadCountSuites() : 0;
+        concurrency.classes = params.isParallelClasses() ? params.getThreadCountClasses() : 0;
+        concurrency.methods = params.isParallelMethod() ? params.getThreadCountMethods() : 0;
+        concurrency.capacity = params.getThreadCount();
+        double all = sumThreadCounts( concurrency );
+
+        concurrency.suites = params.isParallelSuites() ?
+                multiplyByCoreCount( params, concurrency.capacity * ( concurrency.suites / all ) ) : 0;
+
+        concurrency.classes = params.isParallelClasses() ?
+                multiplyByCoreCount( params, concurrency.capacity * ( concurrency.classes / all ) ) : 0;
+
+        concurrency.methods = params.isParallelMethod() ?
+                multiplyByCoreCount( params, concurrency.capacity * ( concurrency.methods / all ) ) : 0;
+
+        concurrency.capacity = multiplyByCoreCount( params, concurrency.capacity );
+        adjustPrecisionInLeaf( params, concurrency );
+        return concurrency;
+    }
+
+    private static Concurrency concurrencyFromThreadCounts( JUnitCoreParameters params )
+    {
+        Concurrency concurrency = new Concurrency();
+        concurrency.suites = params.isParallelSuites() ? threadCountSuites( params ) : 0;
+        concurrency.classes = params.isParallelClasses() ? threadCountClasses( params ) : 0;
+        concurrency.methods = params.isParallelMethod() ? threadCountMethods( params ) : 0 ;
+        concurrency.capacity = (int) Math.min( sumThreadCounts( concurrency ), Integer.MAX_VALUE );
+        return concurrency;
+    }
+
+    private static int countParallelEntities( JUnitCoreParameters params )
+    {
+        int count = 0;
+        if ( params.isParallelSuites() ) count++;
+        if ( params.isParallelClasses() ) count++;
+        if ( params.isParallelMethod() ) count++;
+        return count;
+    }
+
+    private static void adjustPrecisionInLeaf( JUnitCoreParameters params, Concurrency concurrency )
+    {
+        if ( params.isParallelMethod() )
+        {
+            concurrency.methods = concurrency.capacity - concurrency.suites - concurrency.classes;
+        }
+        else if ( params.isParallelClasses() )
+        {
+            concurrency.classes = concurrency.capacity - concurrency.suites;
+        }
+    }
+
+    private static void adjustLeaf( JUnitCoreParameters params, Concurrency concurrency )
+    {
+        if ( params.isParallelMethod() )
+        {
+            concurrency.methods = Integer.MAX_VALUE;
+        }
+        else if ( params.isParallelClasses() )
+        {
+            concurrency.classes = Integer.MAX_VALUE;
+        }
+    }
+
+    private static void setLeafInfinite( JUnitCoreParameters params, Concurrency concurrency )
+    {
+        if ( params.isParallelMethod() ) concurrency.methods = Integer.MAX_VALUE;
+        else if ( params.isParallelClasses() ) concurrency.classes = Integer.MAX_VALUE;
+        else if ( params.isParallelSuites() ) concurrency.suites = Integer.MAX_VALUE;
+    }
+
+    private static boolean isLeafUnspecified( JUnitCoreParameters params )
+    {
+        int maskOfParallel = params.isParallelSuites() ? 4: 0;
+        maskOfParallel |= params.isParallelClasses() ? 2 : 0;
+        maskOfParallel |= params.isParallelMethod() ? 1 : 0;
+
+        int maskOfConcurrency = params.getThreadCountSuites() > 0 ? 4 : 0;
+        maskOfConcurrency |= params.getThreadCountClasses() > 0 ? 2 : 0;
+        maskOfConcurrency |= params.getThreadCountMethods() > 0 ? 1 : 0;
+
+        maskOfConcurrency &= maskOfParallel;
+
+        int leaf = Integer.lowestOneBit( maskOfParallel );
+        return maskOfConcurrency == maskOfParallel - leaf;
+    }
+
+    private static double sumThreadCounts( Concurrency concurrency )
+    {
+        double sum = concurrency.suites;
+        sum += concurrency.classes;
+        sum += concurrency.methods;
+        return sum;
+    }
+
+    private static boolean hasThreadCounts( JUnitCoreParameters jUnitCoreParameters )
+    {
+        return jUnitCoreParameters.getThreadCountSuites() > 0 ||
+                jUnitCoreParameters.getThreadCountClasses() > 0 ||
+                jUnitCoreParameters.getThreadCountMethods() > 0;
+    }
+
+    private static boolean hasThreadCount ( JUnitCoreParameters jUnitCoreParameters )
+    {
+        return jUnitCoreParameters.getThreadCount() > 0;
+    }
+
+    private static int threadCountMethods( JUnitCoreParameters jUnitCoreParameters )
+    {
+        return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountMethods() );
+    }
+
+    private static int threadCountClasses( JUnitCoreParameters jUnitCoreParameters )
+    {
+        return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountClasses() );
+    }
+
+    private static int threadCountSuites( JUnitCoreParameters jUnitCoreParameters )
+    {
+        return multiplyByCoreCount( jUnitCoreParameters, jUnitCoreParameters.getThreadCountSuites() );
+    }
+
+    private static int multiplyByCoreCount( JUnitCoreParameters jUnitCoreParameters, double threadsPerCore )
+    {
+        double numberOfThreads =
+                    jUnitCoreParameters.isPerCoreThreadCount() ?
+                            threadsPerCore * (double) availableProcessors : threadsPerCore;
+
+        return numberOfThreads > 0 ? (int) Math.min( numberOfThreads, Integer.MAX_VALUE ) : Integer.MAX_VALUE;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java
new file mode 100644
index 0000000..fea0701
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/AbstractThreadPoolStrategy.java
@@ -0,0 +1,111 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.util.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Abstract parallel scheduling strategy in private package.
+ * The remaining abstract methods have to be implemented differently
+ * depending if the thread pool is shared with other strategies or not.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see SchedulingStrategy
+ * @see SharedThreadPoolStrategy
+ * @see NonSharedThreadPoolStrategy
+ */
+abstract class AbstractThreadPoolStrategy extends SchedulingStrategy {
+    private final ExecutorService threadPool;
+    private final Collection<Future<?>> futureResults;
+    private final AtomicBoolean canSchedule = new AtomicBoolean(true);
+
+    AbstractThreadPoolStrategy(ExecutorService threadPool) {
+        this(threadPool, null);
+    }
+
+    AbstractThreadPoolStrategy(ExecutorService threadPool, Collection<Future<?>> futureResults) {
+        this.threadPool = threadPool;
+        this.futureResults = futureResults;
+    }
+
+    protected final ExecutorService getThreadPool() {
+        return threadPool;
+    }
+
+    protected final Collection<Future<?>> getFutureResults() {
+        return futureResults;
+    }
+
+    protected final void disable() {
+        canSchedule.set(false);
+    }
+
+    @Override
+    public void schedule(Runnable task) {
+        if (canSchedule()) {
+            Future<?> futureResult = threadPool.submit(task);
+            if (futureResults != null) {
+                futureResults.add(futureResult);
+            }
+        }
+    }
+
+    @Override
+    protected boolean stop() {
+        boolean wasRunning = canSchedule.getAndSet(false);
+        if (threadPool.isShutdown()) {
+            wasRunning = false;
+        } else {
+            threadPool.shutdown();
+        }
+        return wasRunning;
+    }
+
+    @Override
+    protected boolean stopNow() {
+        boolean wasRunning = canSchedule.getAndSet(false);
+        if (threadPool.isShutdown()) {
+            wasRunning = false;
+        } else {
+            threadPool.shutdownNow();
+        }
+        return wasRunning;
+    }
+
+    @Override
+    protected void setDefaultShutdownHandler(Scheduler.ShutdownHandler handler) {
+        if (threadPool instanceof ThreadPoolExecutor) {
+            ThreadPoolExecutor pool = (ThreadPoolExecutor) threadPool;
+            handler.setRejectedExecutionHandler(pool.getRejectedExecutionHandler());
+            pool.setRejectedExecutionHandler(handler);
+        }
+    }
+
+    @Override
+    public final boolean canSchedule() {
+        return canSchedule.get();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Balancer.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Balancer.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Balancer.java
new file mode 100644
index 0000000..1b28309
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/Balancer.java
@@ -0,0 +1,49 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.
+ */
+
+/**
+ * The Balancer controls the maximum of concurrent threads in the current Scheduler(s) and prevents
+ * from own thread resources exhaustion if other group of schedulers share the same pool of threads.
+ * <p>
+ * If a permit is available, {@link #acquirePermit()} simply returns and a new test is scheduled
+ * by {@link Scheduler#schedule(Runnable)} in the current runner. Otherwise waiting for a release.
+ * One permit is released as soon as the child thread has finished.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ */
+public interface Balancer {
+
+    /**
+     * Acquires a permit from this balancer, blocking until one is available.
+     *
+     * @return <code>true</code> if current thread is <em>NOT</em> interrupted
+     *         while waiting for a permit.
+     */
+    public boolean acquirePermit();
+
+    /**
+     * Releases a permit, returning it to the balancer.
+     */
+    public void releasePermit();
+
+    public void releaseAllPermits();
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/BalancerFactory.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/BalancerFactory.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/BalancerFactory.java
new file mode 100644
index 0000000..e7db197
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/BalancerFactory.java
@@ -0,0 +1,68 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.
+ */
+
+/**
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see Balancer
+ */
+public class BalancerFactory {
+    private BalancerFactory()
+    {
+    }
+
+    /**
+     * Infinite permits.
+     */
+    public static Balancer createInfinitePermitsBalancer()
+    {
+        return balancer( 0, false );
+    }
+
+    /**
+     * Balancer without fairness.
+     * Fairness guarantees the waiting schedulers to wake up in order they acquired a permit.
+     *
+     * @param concurrency number of permits to acquire when maintaining concurrency on tests
+     */
+    public static Balancer createBalancer( int concurrency )
+    {
+        return balancer( concurrency, false );
+    }
+
+    /**
+     * Balancer with fairness.
+     * Fairness guarantees the waiting schedulers to wake up in order they acquired a permit.
+     *
+     * @param concurrency number of permits to acquire when maintaining concurrency on tests
+     */
+    public static Balancer createBalancerWithFairness( int concurrency )
+    {
+        return balancer( concurrency, true );
+    }
+
+    private static Balancer balancer( int concurrency, boolean fairness )
+    {
+        boolean shouldBalance = concurrency > 0 && concurrency < Integer.MAX_VALUE;
+        return shouldBalance ? new ThreadResourcesBalancer( concurrency, fairness ) : new NullBalancer();
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java
new file mode 100644
index 0000000..dc1e5b3
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/InvokerStrategy.java
@@ -0,0 +1,61 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * The sequentially executing strategy in private package.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see SchedulingStrategy
+ */
+final class InvokerStrategy extends SchedulingStrategy {
+    private final AtomicBoolean canSchedule = new AtomicBoolean(true);
+
+    @Override
+    public void schedule(Runnable task) {
+        if (canSchedule()) {
+            task.run();
+        }
+    }
+
+    @Override
+    protected boolean stop() {
+        return canSchedule.getAndSet(false);
+    }
+
+    @Override
+    public boolean hasSharedThreadPool() {
+        return false;
+    }
+
+    @Override
+    public boolean canSchedule() {
+        return canSchedule.get();
+    }
+
+    @Override
+    public boolean finished() throws InterruptedException {
+        return stop();
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java
new file mode 100644
index 0000000..b463605
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NonSharedThreadPoolStrategy.java
@@ -0,0 +1,54 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Parallel strategy for non-shared thread pool in private package.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see AbstractThreadPoolStrategy
+ */
+final class NonSharedThreadPoolStrategy extends AbstractThreadPoolStrategy {
+    NonSharedThreadPoolStrategy(ExecutorService threadPool) {
+        super(threadPool);
+    }
+
+    @Override
+    public boolean hasSharedThreadPool() {
+        return false;
+    }
+
+    @Override
+    public boolean finished() throws InterruptedException {
+        boolean wasRunning = canSchedule();
+        getThreadPool().shutdown();
+        try {
+            getThreadPool().awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
+            return wasRunning;
+        } finally {
+            disable();
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/49c4a625/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NullBalancer.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NullBalancer.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NullBalancer.java
new file mode 100644
index 0000000..eec3759
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/NullBalancer.java
@@ -0,0 +1,44 @@
+package org.apache.maven.surefire.junitcore.pc;
+
+/*
+ * 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.
+ */
+
+/**
+ * This balancer implements {@link Balancer} and does not do anything -no blocking operation.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.16
+ *
+ * @see Balancer
+ */
+final class NullBalancer implements Balancer
+{
+    public boolean acquirePermit()
+    {
+        return true;
+    }
+
+    public void releasePermit()
+    {
+    }
+
+    public void releaseAllPermits()
+    {
+    }
+}
\ No newline at end of file