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:58 UTC

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

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();
+    }
 }