maven-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tibordig...@apache.org
Subject git commit: Exceptional parallel execution on @NotThreadSafe type.
Date Sat, 13 Sep 2014 21:17:30 GMT
Repository: maven-surefire
Updated Branches:
  refs/heads/master 29f76bcb5 -> 7f2c1fb11


Exceptional parallel execution on @NotThreadSafe type.


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

Branch: refs/heads/master
Commit: 7f2c1fb11f1fab043586f78621ef2fae69eda130
Parents: 29f76bc
Author: Tibor Digana <tibor17@lycos.com>
Authored: Tue Sep 2 00:44:15 2014 +0200
Committer: tibordigana <tibor17@lycos.com>
Committed: Sat Sep 13 22:43:55 2014 +0200

----------------------------------------------------------------------
 .../fork-options-and-parallel-execution.apt.vm  |  23 +-
 .../src/test/resources/junit47-parallel/pom.xml |  26 +-
 surefire-providers/surefire-junit47/pom.xml     |   6 +
 .../surefire/junitcore/pc/ExecutionStatus.java  |  35 +++
 .../surefire/junitcore/pc/InvokerStrategy.java  |  26 +-
 .../surefire/junitcore/pc/ParallelComputer.java | 143 ++++++----
 .../junitcore/pc/ParallelComputerBuilder.java   | 118 ++++++---
 .../maven/surefire/junitcore/pc/Scheduler.java  |   8 +-
 .../surefire/junitcore/pc/ShutdownStatus.java   |  68 +++++
 .../junitcore/pc/SingleThreadScheduler.java     |  70 +++++
 .../surefire/junitcore/pc/WrappedRunners.java   |   7 +-
 .../pc/ParallelComputerBuilderTest.java         | 260 ++++++++++++++++++-
 .../junitcore/pc/ParallelComputerUtilTest.java  |  54 +++-
 13 files changed, 739 insertions(+), 105 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
----------------------------------------------------------------------
diff --git a/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm b/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
index 02b22ed..77b4b48 100644
--- a/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/fork-options-and-parallel-execution.apt.vm
@@ -93,7 +93,7 @@ Fork Options and Parallel Test Execution
   
   The surefire is always trying to reuse threads, optimize the thread-counts,
   and prefers thread fairness.
-  
+
   <<The important thing to remember>> with the <<<parallel>>> option is: the
   concurrency happens within the same JVM process. That is efficient in terms of
   memory and execution time, but you may be more vulnerable towards race
@@ -103,6 +103,25 @@ Fork Options and Parallel Test Execution
   <<<forkCount>>> to a value higher than 1. The next section covers the details 
   about this and the related <<<reuseForks>>> property.
 
+  * Parallel Test Execution and Single Thread Execution
+
+  As mentioned above the <<<parallel>>> test execution is used with specific
+  thread count. Since of Surefire 2.18, you can apply the JCIP annotation
+  <<<@net.jcip.annotations.NotThreadSafe>>> on the Java class of JUnit test
+  (test class, Suite, Parameterized, etc.) in order to execute it in single
+  Thread instance. The Thread has name "maven-surefire-plugin@NotThreadSafe".
+  Just use the dependency net.jcip:jcip-annotations:1.0, or another Artifact
+  with Apache License com.github.stephenc.jcip:jcip-annotations:1.0-1. This
+  way parallel execution of tests classes annotated with <<<@NotThreadSafe>>>
+  are forked in single thread instance (don't mean forked JVM process).
+  If the Suite or Parameterized is annotated with @NotThreadSafe, the
+  suite classes are executed in single thread.
+  You can also annotate test class referenced by Suite, and the other
+  unannotated test classes in the Suite can be subject to run in parallel.
+  Note: As designed by JUnit runners, the static methods annotated with
+  @BeforeClass and @AfterClass are called in parent thread. Assign classes
+  to the @NotThreadSafe Suite to prevent from this trouble.
+
 * Parallel Surefire Execution in Multi-Module Maven Parallel Build
 
   Maven core allows building modules of multi-module projects in parallel with
@@ -113,7 +132,7 @@ Fork Options and Parallel Test Execution
 
   The parameter <<<forkCount>>> defines the maximum number of JVM processes
   that Surefire will spawn <concurrently> to execute the tests. It supports the
-  same syntax as <<<-T>>> in maven-core: if you termniate the value with a 'C',
+  same syntax as <<<-T>>> in maven-core: if you terminate the value with a 'C',
   that value will be multiplied with the number of available CPU cores in your
   system. For example <<<forkCount=2.5C>>> on a Quad-Core system will result
   in forking up to ten concurrent JVM processes that execute tests.

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/surefire-integration-tests/src/test/resources/junit47-parallel/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-integration-tests/src/test/resources/junit47-parallel/pom.xml b/surefire-integration-tests/src/test/resources/junit47-parallel/pom.xml
index b4baa02..4f697de 100644
--- a/surefire-integration-tests/src/test/resources/junit47-parallel/pom.xml
+++ b/surefire-integration-tests/src/test/resources/junit47-parallel/pom.xml
@@ -1,6 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
   <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.maven.surefire</groupId>
+    <artifactId>it-parent</artifactId>
+    <version>1.0</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
   <groupId>org.apache.maven.plugins.surefire</groupId>
   <artifactId>junit47-parallel</artifactId>
   <version>1.0-SNAPSHOT</version>
@@ -32,7 +57,6 @@
       </plugin>
       <plugin>
         <artifactId>maven-surefire-plugin</artifactId>
-        <version>${surefire.version}</version>
       </plugin>
     </plugins>
   </build>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/surefire-providers/surefire-junit47/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/pom.xml b/surefire-providers/surefire-junit47/pom.xml
index 6b5a932..6dcbad7 100644
--- a/surefire-providers/surefire-junit47/pom.xml
+++ b/surefire-providers/surefire-junit47/pom.xml
@@ -33,6 +33,11 @@
 
   <dependencies>
     <dependency>
+      <groupId>com.github.stephenc.jcip</groupId>
+      <artifactId>jcip-annotations</artifactId>
+      <version>1.0-1</version>
+    </dependency>
+    <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
       <version>4.8.1</version>
@@ -74,6 +79,7 @@
         <configuration>
           <jvm>${java.home}/bin/java</jvm>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
+          <argLine>-server -Xmx128m -XX:+UseParallelGC -XX:+UseNUMA -XX:MaxGCPauseMillis=50</argLine>
         </configuration>
       </plugin>
       <plugin>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ExecutionStatus.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ExecutionStatus.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ExecutionStatus.java
new file mode 100644
index 0000000..fbc05f0
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ExecutionStatus.java
@@ -0,0 +1,35 @@
+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.
+ */
+
+/**
+ * Status of {@link ParallelComputer ParallelComputer runtime}.<p/>
+ * Used together with shutdown hook.
+ *
+ * @author <a href="mailto:tibor.digana@gmail.com">Tibor Digana (tibor17)</a>
+ * @see ParallelComputer
+ * @since 2.18
+ */
+enum ExecutionStatus
+{
+    STARTED,
+    FINISHED,
+    TIMEOUT
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/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
index 29a6624..4dd7f10 100644
--- 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
@@ -19,6 +19,8 @@ package org.apache.maven.surefire.junitcore.pc;
  * under the License.
  */
 
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -33,12 +35,23 @@ final class InvokerStrategy
 {
     private final AtomicBoolean canSchedule = new AtomicBoolean( true );
 
+    private final Queue<Thread> activeThreads = new ConcurrentLinkedQueue<Thread>();
+
     @Override
     public void schedule( Runnable task )
     {
         if ( canSchedule() )
         {
-            task.run();
+            final Thread currentThread = Thread.currentThread();
+            try
+            {
+                activeThreads.add( currentThread );
+                task.run();
+            }
+            finally
+            {
+                activeThreads.remove( currentThread );
+            }
         }
     }
 
@@ -49,6 +62,17 @@ final class InvokerStrategy
     }
 
     @Override
+    protected boolean stopNow()
+    {
+        final boolean stopped = stop();
+        for ( Thread activeThread; ( activeThread = activeThreads.poll() ) != null; )
+        {
+            activeThread.interrupt();
+        }
+        return stopped;
+    }
+
+    @Override
     public boolean hasSharedThreadPool()
     {
         return false;

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/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
index 79ca87a..bf28c70 100644
--- 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
@@ -24,14 +24,14 @@ import org.junit.runner.Computer;
 import org.junit.runner.Description;
 
 import java.util.Collection;
-import java.util.Collections;
 import java.util.TreeSet;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 
-import static java.util.concurrent.TimeUnit.*;
+import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
 /**
  * ParallelComputer extends JUnit {@link Computer} and has a shutdown functionality.
@@ -43,17 +43,17 @@ import static java.util.concurrent.TimeUnit.*;
 public abstract class ParallelComputer
     extends Computer
 {
+    private final ShutdownStatus shutdownStatus = new ShutdownStatus();
+
+    private final ShutdownStatus forcedShutdownStatus = new ShutdownStatus();
+
     private final long timeoutNanos;
 
     private final long timeoutForcedNanos;
 
     private ScheduledExecutorService shutdownScheduler;
 
-    private Future<Collection<Description>> testsBeforeShutdown;
-
-    private Future<Collection<Description>> testsBeforeForcedShutdown;
-
-    public ParallelComputer( double timeoutInSeconds, double timeoutForcedInSeconds  )
+    public ParallelComputer( double timeoutInSeconds, double timeoutForcedInSeconds )
     {
         this.timeoutNanos = secondsToNanos( timeoutInSeconds );
         this.timeoutForcedNanos = secondsToNanos( timeoutForcedInSeconds );
@@ -81,71 +81,96 @@ public abstract class ParallelComputer
         }
     }
 
-    private static Collection<String> printShutdownHook( Future<Collection<Description>> future )
-        throws TestSetFailedException
+    private static void printShutdownHook( Collection<String> executedTests,
+                                           Future<Collection<Description>> testsBeforeShutdown )
+        throws ExecutionException, InterruptedException
     {
-        if ( !future.isCancelled() && future.isDone() )
+        if ( testsBeforeShutdown != null )
         {
-            try
+            for ( final Description executedTest : testsBeforeShutdown.get() )
             {
-                TreeSet<String> executedTests = new TreeSet<String>();
-                for ( Description executedTest : future.get() )
+                if ( executedTest != null && executedTest.getDisplayName() != null )
                 {
-                    if ( executedTest != null && executedTest.getDisplayName() != null )
-                    {
-                        executedTests.add( executedTest.getDisplayName() );
-                    }
+                    executedTests.add( executedTest.getDisplayName() );
                 }
-                return executedTests;
-            }
-            catch ( Exception e )
-            {
-                throw new TestSetFailedException( e );
             }
         }
-        return Collections.emptySet();
     }
 
     public abstract Collection<Description> shutdown( boolean shutdownNow );
 
     protected final void beforeRunQuietly()
     {
-        testsBeforeShutdown = timeoutNanos > 0 ? scheduleShutdown() : null;
-        testsBeforeForcedShutdown = timeoutForcedNanos > 0 ? scheduleForcedShutdown() : null;
+        shutdownStatus.setDescriptionsBeforeShutdown( hasTimeout() ? scheduleShutdown() : null );
+        forcedShutdownStatus.setDescriptionsBeforeShutdown( hasTimeoutForced() ? scheduleForcedShutdown() : null );
     }
 
-    protected final void afterRunQuietly()
+    protected final boolean afterRunQuietly()
     {
+        shutdownStatus.tryFinish();
+        forcedShutdownStatus.tryFinish();
         if ( shutdownScheduler != null )
         {
             shutdownScheduler.shutdownNow();
+            /**
+             * Clear <i>interrupted status</i> of the (main) Thread.
+             * Could be previously interrupted by {@link InvokerStrategy} after triggering immediate shutdown.
+             */
+            Thread.interrupted();
+            try
+            {
+                shutdownScheduler.awaitTermination( Long.MAX_VALUE, NANOSECONDS );
+            }
+            catch ( InterruptedException e )
+            {
+                return false;
+            }
         }
+        return true;
     }
 
     public String describeElapsedTimeout()
         throws TestSetFailedException
     {
-        TreeSet<String> executedTests = new TreeSet<String>();
-        if ( testsBeforeShutdown != null )
-        {
-            executedTests.addAll( printShutdownHook( testsBeforeShutdown ) );
-        }
-
-        if ( testsBeforeForcedShutdown != null )
-        {
-            executedTests.addAll( printShutdownHook( testsBeforeForcedShutdown ) );
-        }
-
-        StringBuilder msg = new StringBuilder();
-        if ( !executedTests.isEmpty() )
+        final StringBuilder msg = new StringBuilder();
+        final boolean isShutdownTimeout = shutdownStatus.isTimeoutElapsed();
+        final boolean isForcedShutdownTimeout = forcedShutdownStatus.isTimeoutElapsed();
+        if ( isShutdownTimeout || isForcedShutdownTimeout )
         {
             msg.append( "The test run has finished abruptly after timeout of " );
             msg.append( nanosToSeconds( minTimeout( timeoutNanos, timeoutForcedNanos ) ) );
             msg.append( " seconds.\n" );
-            msg.append( "These tests were executed in prior of the shutdown operation:\n" );
-            for ( String executedTest : executedTests )
+
+            try
+            {
+                final TreeSet<String> executedTests = new TreeSet<String>();
+
+                if ( isShutdownTimeout )
+                {
+                    printShutdownHook( executedTests, shutdownStatus.getDescriptionsBeforeShutdown() );
+                }
+
+                if ( isForcedShutdownTimeout )
+                {
+                    printShutdownHook( executedTests, forcedShutdownStatus.getDescriptionsBeforeShutdown() );
+                }
+
+                if ( !executedTests.isEmpty() )
+                {
+                    msg.append( "These tests were executed in prior to the shutdown operation:\n" );
+                    for ( String executedTest : executedTests )
+                    {
+                        msg.append( executedTest ).append( '\n' );
+                    }
+                }
+            }
+            catch ( InterruptedException e )
             {
-                msg.append( executedTest ).append( '\n' );
+                throw new TestSetFailedException( "Timed termination was interrupted.", e );
+            }
+            catch ( ExecutionException e )
+            {
+                throw new TestSetFailedException( e.getLocalizedMessage(), e.getCause() );
             }
         }
         return msg.toString();
@@ -153,12 +178,12 @@ public abstract class ParallelComputer
 
     private Future<Collection<Description>> scheduleShutdown()
     {
-        return getShutdownScheduler().schedule( createShutdownTask( false ), timeoutNanos, NANOSECONDS );
+        return getShutdownScheduler().schedule( createShutdownTask(), timeoutNanos, NANOSECONDS );
     }
 
     private Future<Collection<Description>> scheduleForcedShutdown()
     {
-        return getShutdownScheduler().schedule( createShutdownTask( true ), timeoutForcedNanos, NANOSECONDS );
+        return getShutdownScheduler().schedule( createForcedShutdownTask(), timeoutForcedNanos, NANOSECONDS );
     }
 
     private ScheduledExecutorService getShutdownScheduler()
@@ -170,14 +195,28 @@ public abstract class ParallelComputer
         return shutdownScheduler;
     }
 
-    private Callable<Collection<Description>> createShutdownTask( final boolean isForced )
+    private Callable<Collection<Description>> createShutdownTask()
+    {
+        return new Callable<Collection<Description>>()
+        {
+            public Collection<Description> call()
+                throws Exception
+            {
+                boolean stampedStatusWithTimeout = ParallelComputer.this.shutdownStatus.tryTimeout();
+                return stampedStatusWithTimeout ? ParallelComputer.this.shutdown( false ) : null;
+            }
+        };
+    }
+
+    private Callable<Collection<Description>> createForcedShutdownTask()
     {
         return new Callable<Collection<Description>>()
         {
             public Collection<Description> call()
                 throws Exception
             {
-                return ParallelComputer.this.shutdown( isForced );
+                boolean stampedStatusWithTimeout = ParallelComputer.this.forcedShutdownStatus.tryTimeout();
+                return stampedStatusWithTimeout ? ParallelComputer.this.shutdown( true ) : null;
             }
         };
     }
@@ -186,4 +225,14 @@ public abstract class ParallelComputer
     {
         return (double) nanos / 1E9;
     }
-}
\ No newline at end of file
+
+    private boolean hasTimeout()
+    {
+        return timeoutNanos > 0;
+    }
+
+    private boolean hasTimeoutForced()
+    {
+        return timeoutForcedNanos > 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/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
index 04813ad..ca7cc60 100644
--- 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
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.junitcore.pc;
  * under the License.
  */
 
+import net.jcip.annotations.NotThreadSafe;
 import org.apache.maven.surefire.junitcore.JUnitCoreParameters;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.junit.internal.runners.ErrorReportingRunner;
@@ -33,12 +34,15 @@ import org.junit.runners.model.InitializationError;
 import org.junit.runners.model.RunnerBuilder;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -71,6 +75,8 @@ import static org.apache.maven.surefire.junitcore.pc.Type.*;
  */
 public final class ParallelComputerBuilder
 {
+    private static final Set<?> NULL_SINGLETON = Collections.singleton( null );
+
     static final int TOTAL_POOL_SIZE_UNDEFINED = 0;
 
     private final Map<Type, Integer> parallelGroups = new EnumMap<Type, Integer>( Type.class );
@@ -217,6 +223,8 @@ public final class ParallelComputerBuilder
     final class PC
         extends ParallelComputer
     {
+        private final SingleThreadScheduler notThreadSafeTests = new SingleThreadScheduler();
+
         final Collection<ParentRunner> suites = new LinkedHashSet<ParentRunner>();
 
         final Collection<ParentRunner> nestedSuites = new LinkedHashSet<ParentRunner>();
@@ -225,7 +233,7 @@ public final class ParallelComputerBuilder
 
         final Collection<ParentRunner> nestedClasses = new LinkedHashSet<ParentRunner>();
 
-        final Collection<Runner> unscheduledRunners = new LinkedHashSet<Runner>();
+        final Collection<Runner> notParallelRunners = new LinkedHashSet<Runner>();
 
         int poolCapacity;
 
@@ -248,8 +256,13 @@ public final class ParallelComputerBuilder
         @Override
         public Collection<Description> shutdown( boolean shutdownNow )
         {
-            final Scheduler master = this.master;
-            return master == null ? Collections.<Description>emptyList() : master.shutdown( shutdownNow );
+            Collection<Description> startedTests = notThreadSafeTests.shutdown( shutdownNow );
+            final Scheduler m = this.master;
+            if ( m != null )
+            {
+                startedTests.addAll( m.shutdown( shutdownNow ) );
+            }
+            return startedTests;
         }
 
         @Override
@@ -287,7 +300,12 @@ public final class ParallelComputerBuilder
             Runner runner = super.getRunner( builder, testClass );
             if ( canSchedule( runner ) )
             {
-                if ( runner instanceof Suite )
+                if ( !isThreadSafe( runner ) )
+                {
+                    ( ( ParentRunner ) runner ).setScheduler( notThreadSafeTests.newRunnerScheduler() );
+                    notParallelRunners.add( runner );
+                }
+                else if ( runner instanceof Suite )
                 {
                     suites.add( (Suite) runner );
                 }
@@ -298,7 +316,7 @@ public final class ParallelComputerBuilder
             }
             else
             {
-                unscheduledRunners.add( runner );
+                notParallelRunners.add( runner );
             }
             return runner;
         }
@@ -335,11 +353,7 @@ public final class ParallelComputerBuilder
                     }
                 }
             }
-
-            Suite wrapper = runs.isEmpty() ? null : new Suite( null, runs )
-            {
-            };
-            return new WrappedRunners( wrapper, childrenCounter );
+            return runs.isEmpty() ? new WrappedRunners() : new WrappedRunners( createSuite( runs ), childrenCounter );
         }
 
         private int countChildren( Runner runner )
@@ -358,7 +372,8 @@ public final class ParallelComputerBuilder
 
         private Scheduler createMaster( ExecutorService pool, int poolSize )
         {
-            if ( !areSuitesAndClassesParallel() || poolSize <= 1 )
+            final int finalRunnersCounter = countFinalRunners(); // can be 0, 1, 2 or 3
+            if ( finalRunnersCounter <= 1 || poolSize <= 1 )
             {
                 return new Scheduler( null, new InvokerStrategy() );
             }
@@ -368,14 +383,25 @@ public final class ParallelComputerBuilder
             }
             else
             {
-                return new Scheduler( null, SchedulingStrategies.createParallelStrategy( 2 ) );
+                return new Scheduler( null, SchedulingStrategies.createParallelStrategy( finalRunnersCounter ) );
             }
         }
 
-        private boolean areSuitesAndClassesParallel()
+        private int countFinalRunners()
         {
-            return !suites.isEmpty() && allGroups.get( SUITES ) > 0 && !classes.isEmpty()
-                && allGroups.get( CLASSES ) > 0;
+            int counter = notParallelRunners.isEmpty() ? 0 : 1;
+
+            if ( !suites.isEmpty() && allGroups.get( SUITES ) > 0 )
+            {
+                ++counter;
+            }
+
+            if ( !classes.isEmpty() && allGroups.get( CLASSES ) > 0 )
+            {
+                ++counter;
+            }
+
+            return counter;
         }
 
         private void populateChildrenFromSuites()
@@ -463,24 +489,17 @@ public final class ParallelComputerBuilder
             }
 
             // resulting runner for Computer#getSuite() scheduled by master scheduler
-            ParentRunner all = createFinalRunner( suiteSuites, suiteClasses );
+            ParentRunner all = createFinalRunner( removeNullRunners(
+                Arrays.<Runner>asList( suiteSuites, suiteClasses, createSuite( notParallelRunners ) )
+            ) );
             all.setScheduler( master );
             return all;
         }
 
-        private ParentRunner createFinalRunner( Runner... runners )
+        private ParentRunner createFinalRunner( List<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 )
+            return new Suite( null, runners )
             {
                 @Override
                 public void run( RunNotifier notifier )
@@ -559,6 +578,11 @@ public final class ParallelComputerBuilder
             return !( runner instanceof ErrorReportingRunner ) && runner instanceof ParentRunner;
         }
 
+        private boolean isThreadSafe( Runner runner )
+        {
+            return runner.getDescription().getAnnotation( NotThreadSafe.class ) == null;
+        }
+
         private class SuiteFilter
             extends Filter
         {
@@ -575,15 +599,23 @@ public final class ParallelComputerBuilder
                 throws NoTestsRemainException
             {
                 super.apply( child );
-                if ( child instanceof Suite )
+                if ( child instanceof ParentRunner )
                 {
-                    nestedSuites.add( (Suite) child );
-                }
-                else if ( child instanceof ParentRunner )
-                {
-                    ParentRunner parentRunner = (ParentRunner) child;
-                    nestedClasses.add( parentRunner );
-                    nestedClassesChildren += parentRunner.getDescription().getChildren().size();
+                    ParentRunner runner = ( ParentRunner ) child;
+                    if ( !isThreadSafe( runner ) )
+                    {
+                        runner.setScheduler( notThreadSafeTests.newRunnerScheduler() );
+                    }
+                    else if ( child instanceof Suite )
+                    {
+                        nestedSuites.add( (Suite) child );
+                    }
+                    else
+                    {
+                        ParentRunner parentRunner = (ParentRunner) child;
+                        nestedClasses.add( parentRunner );
+                        nestedClassesChildren += parentRunner.getDescription().getChildren().size();
+                    }
                 }
             }
 
@@ -594,4 +626,20 @@ public final class ParallelComputerBuilder
             }
         }
     }
+
+    private static Suite createSuite( Collection<Runner> runners )
+        throws InitializationError
+    {
+        final List<Runner> onlyRunners = removeNullRunners( runners );
+        return onlyRunners.isEmpty() ? null : new Suite( null, onlyRunners )
+        {
+        };
+    }
+
+    private static List<Runner> removeNullRunners( Collection<Runner> runners )
+    {
+        final List<Runner> onlyRunners = new ArrayList<Runner>( runners );
+        onlyRunners.removeAll( NULL_SINGLETON );
+        return onlyRunners;
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/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
index d3c4133..a16f1d2 100644
--- 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
@@ -22,9 +22,9 @@ package org.apache.maven.surefire.junitcore.pc;
 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.ConcurrentLinkedQueue;
 import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.RejectedExecutionHandler;
@@ -190,12 +190,12 @@ public class Scheduler
 
     protected void logQuietly( Throwable t )
     {
-        t.printStackTrace( System.err );
+        t.printStackTrace( System.out );
     }
 
     protected void logQuietly( String msg )
     {
-        System.err.println( msg );
+        System.out.println( msg );
     }
 
     /**
@@ -211,7 +211,7 @@ public class Scheduler
     public Collection<Description> shutdown( boolean shutdownNow )
     {
         shutdown = true;
-        ArrayList<Description> activeChildren = new ArrayList<Description>();
+        Collection<Description> activeChildren = new ConcurrentLinkedQueue<Description>();
 
         if ( started && description != null )
         {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ShutdownStatus.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ShutdownStatus.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ShutdownStatus.java
new file mode 100644
index 0000000..1bed62d
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/ShutdownStatus.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.
+ */
+
+import org.junit.runner.Description;
+
+import java.util.Collection;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static org.apache.maven.surefire.junitcore.pc.ExecutionStatus.*;
+
+/**
+ * Wrapper of {@link ParallelComputer ParallelComputer status information} and tests been populated before
+ * a shutdown hook has been triggered.
+ *
+ * @author <a href="mailto:tibor.digana@gmail.com">Tibor Digana (tibor17)</a>
+ * @see ParallelComputer
+ * @since 2.18
+ */
+final class ShutdownStatus
+{
+    private final AtomicReference<ExecutionStatus> status = new AtomicReference<ExecutionStatus>( STARTED );
+
+    private Future<Collection<Description>> descriptionsBeforeShutdown;
+
+    boolean tryFinish()
+    {
+        return status.compareAndSet( STARTED, FINISHED );
+    }
+
+    boolean tryTimeout()
+    {
+        return status.compareAndSet( STARTED, TIMEOUT );
+    }
+
+    boolean isTimeoutElapsed()
+    {
+        return status.get() == TIMEOUT;
+    }
+
+    Future<Collection<Description>> getDescriptionsBeforeShutdown()
+    {
+        return descriptionsBeforeShutdown;
+    }
+
+    void setDescriptionsBeforeShutdown( Future<Collection<Description>> descriptionsBeforeShutdown )
+    {
+        this.descriptionsBeforeShutdown = descriptionsBeforeShutdown;
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java
new file mode 100644
index 0000000..79b3197
--- /dev/null
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/SingleThreadScheduler.java
@@ -0,0 +1,70 @@
+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.Collection;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Used to execute tests annotated with {@link net.jcip.annotations.NotThreadSafe}.
+ * <p/>
+ *
+ * @author <a href="mailto:tibor.digana@gmail.com">Tibor Digana (tibor17)</a>
+ * @see ParallelComputerBuilder
+ * @since 2.18
+ */
+final class SingleThreadScheduler
+{
+    private final ExecutorService pool = newPool();
+
+    private final Scheduler master = new Scheduler( null, SchedulingStrategies.createParallelSharedStrategy( pool ) );
+
+    RunnerScheduler newRunnerScheduler()
+    {
+        return new Scheduler( null, master, SchedulingStrategies.createParallelSharedStrategy( pool ) );
+    }
+
+    /**
+     * @see Scheduler#shutdown(boolean)
+     */
+    Collection<Description> shutdown( boolean shutdownNow )
+    {
+        return master.shutdown( shutdownNow );
+    }
+
+    private static ExecutorService newPool()
+    {
+        final ThreadFactory factory = new ThreadFactory()
+        {
+            public Thread newThread( Runnable r )
+            {
+                return new Thread( r, "maven-surefire-plugin@NotThreadSafe" );
+            }
+        };
+        return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), factory);
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/WrappedRunners.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/WrappedRunners.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/WrappedRunners.java
index 4550b5e..ba45e74 100644
--- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/WrappedRunners.java
+++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/pc/WrappedRunners.java
@@ -43,4 +43,9 @@ final class WrappedRunners
         this.wrappingSuite = wrappingSuite;
         this.embeddedChildrenCount = embeddedChildrenCount;
     }
-}
\ No newline at end of file
+
+    WrappedRunners()
+    {
+        this( null, 0 );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/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
index e3fbf3a..cad3062 100644
--- 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
@@ -19,6 +19,7 @@ package org.apache.maven.surefire.junitcore.pc;
  * under the License.
  */
 
+import net.jcip.annotations.NotThreadSafe;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -35,14 +36,11 @@ 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;
+import static org.junit.Assert.*;
 
 /**
  * @author Tibor Digana (tibor17)
@@ -84,6 +82,11 @@ public class ParallelComputerBuilderTest
         Class1.maxConcurrentMethods = 0;
         Class1.concurrentMethods = 0;
         shutdownTask = null;
+        NotThreadSafeTest1.t = null;
+        NotThreadSafeTest2.t = null;
+        NotThreadSafeTest3.t = null;
+        NormalTest1.t = null;
+        NormalTest2.t = null;
     }
 
     @Test
@@ -394,6 +397,115 @@ public class ParallelComputerBuilderTest
         assertThat( methods.subList( 9, 12 ), is( not( Arrays.asList( "deinit", "deinit", "deinit" ) ) ) );
     }
 
+    @Test
+    public void notThreadSafeTest()
+    {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder()
+            .useOnePool( 6 ).optimize( true ).parallelClasses( 3 ).parallelMethods( 3 );
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) builder.buildComputer();
+        Result result = new JUnitCore().run( computer, NotThreadSafeTest1.class, NotThreadSafeTest2.class );
+        assertTrue( result.wasSuccessful() );
+        assertThat( result.getRunCount(), is( 2 ) );
+        assertNotNull( NotThreadSafeTest1.t );
+        assertNotNull( NotThreadSafeTest2.t );
+        assertSame( NotThreadSafeTest1.t, NotThreadSafeTest2.t );
+        assertThat( computer.notParallelRunners.size(), is( 2 ) );
+        assertTrue( computer.suites.isEmpty() );
+        assertTrue( computer.classes.isEmpty() );
+        assertTrue( computer.nestedClasses.isEmpty() );
+        assertTrue( computer.nestedSuites.isEmpty() );
+        assertFalse( computer.splitPool );
+        assertThat( computer.poolCapacity, is( 6 ) );
+    }
+
+    @Test
+    public void mixedThreadSafety()
+    {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder()
+            .useOnePool( 6 ).optimize( true ).parallelClasses( 3 ).parallelMethods( 3 );
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) builder.buildComputer();
+        Result result = new JUnitCore().run( computer, NotThreadSafeTest1.class, NormalTest1.class );
+        assertTrue( result.wasSuccessful() );
+        assertThat( result.getRunCount(), is( 2 ) );
+        assertNotNull( NotThreadSafeTest1.t );
+        assertNotNull( NormalTest1.t );
+        assertThat( NormalTest1.t.getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        assertNotSame( NotThreadSafeTest1.t, NormalTest1.t );
+        assertThat( computer.notParallelRunners.size(), is( 1 ) );
+        assertTrue( computer.suites.isEmpty() );
+        assertThat( computer.classes.size(), is( 1 ) );
+        assertTrue( computer.nestedClasses.isEmpty() );
+        assertTrue( computer.nestedSuites.isEmpty() );
+        assertFalse( computer.splitPool );
+        assertThat( computer.poolCapacity, is( 6 ) );
+    }
+
+    @Test
+    public void notThreadSafeTestsInSuite()
+    {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder().useOnePool( 5 ).parallelMethods( 3 );
+        assertFalse( builder.isOptimized() );
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) builder.buildComputer();
+        Result result = new JUnitCore().run( computer, NotThreadSafeTestSuite.class );
+        assertTrue( result.wasSuccessful() );
+        assertNotNull( NormalTest1.t );
+        assertNotNull( NormalTest2.t );
+        assertSame( NormalTest1.t, NormalTest2.t );
+        assertThat( NormalTest1.t.getName(), is( "maven-surefire-plugin@NotThreadSafe" ) );
+        assertThat( NormalTest2.t.getName(), is( "maven-surefire-plugin@NotThreadSafe" ) );
+        assertThat( computer.notParallelRunners.size(), is( 1 ) );
+        assertTrue( computer.suites.isEmpty() );
+        assertTrue( computer.classes.isEmpty() );
+        assertTrue( computer.nestedClasses.isEmpty() );
+        assertTrue( computer.nestedSuites.isEmpty() );
+        assertFalse( computer.splitPool );
+        assertThat( computer.poolCapacity, is( 5 ) );
+    }
+
+    @Test
+    public void mixedThreadSafetyInSuite()
+    {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder()
+            .useOnePool( 10 ).optimize( true ).parallelSuites( 2 ).parallelClasses( 3 ).parallelMethods( 3 );
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) builder.buildComputer();
+        Result result = new JUnitCore().run( computer, MixedSuite.class );
+        assertTrue( result.wasSuccessful() );
+        assertThat( result.getRunCount(), is( 2 ) );
+        assertNotNull( NotThreadSafeTest1.t );
+        assertNotNull( NormalTest1.t );
+        assertThat( NormalTest1.t.getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        assertNotSame( NotThreadSafeTest1.t, NormalTest1.t );
+        assertTrue( computer.notParallelRunners.isEmpty() );
+        assertThat( computer.suites.size(), is( 1 ) );
+        assertTrue( computer.classes.isEmpty() );
+        assertThat( computer.nestedClasses.size(), is( 1 ) );
+        assertTrue( computer.nestedSuites.isEmpty() );
+        assertFalse( computer.splitPool );
+        assertThat( computer.poolCapacity, is( 10 ) );
+    }
+
+    @Test
+    public void inheritanceWithNotThreadSafe()
+    {
+        ParallelComputerBuilder builder = new ParallelComputerBuilder()
+            .useOnePool( 10 ).optimize( true ).parallelSuites( 2 ).parallelClasses( 3 ).parallelMethods( 3 );
+        ParallelComputerBuilder.PC computer = (ParallelComputerBuilder.PC) builder.buildComputer();
+        Result result = new JUnitCore().run( computer, OverMixedSuite.class );
+        assertTrue( result.wasSuccessful() );
+        assertThat( result.getRunCount(), is( 2 ) );
+        assertNotNull( NotThreadSafeTest3.t );
+        assertNotNull( NormalTest1.t );
+        assertThat( NormalTest1.t.getName(), is( "maven-surefire-plugin@NotThreadSafe" ) );
+        assertSame( NotThreadSafeTest3.t, NormalTest1.t );
+        assertThat( computer.notParallelRunners.size(), is( 1 ) );
+        assertTrue( computer.suites.isEmpty() );
+        assertTrue( computer.classes.isEmpty() );
+        assertTrue( computer.nestedClasses.isEmpty() );
+        assertTrue( computer.nestedSuites.isEmpty() );
+        assertFalse( computer.splitPool );
+        assertThat( computer.poolCapacity, is( 10 ) );
+    }
+
     private static class ShutdownTest
     {
         Result run( final boolean useInterrupt )
@@ -511,7 +623,145 @@ public class ParallelComputerBuilderTest
 
     @RunWith( Suite.class )
     @Suite.SuiteClasses( { Class2.class, Class1.class } )
-    public class TestSuite
+    public static class TestSuite
+    {
+    }
+
+    @NotThreadSafe
+    public static class NotThreadSafeTest1
+    {
+        static volatile Thread t;
+
+        @BeforeClass
+        public static void beforeSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+
+        @AfterClass
+        public static void afterSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+
+        @Test
+        public void test()
+        {
+            t = Thread.currentThread();
+            assertThat( t.getName(), is( "maven-surefire-plugin@NotThreadSafe" ) );
+        }
+    }
+
+    @NotThreadSafe
+    public static class NotThreadSafeTest2
+    {
+        static volatile Thread t;
+
+        @BeforeClass
+        public static void beforeSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+
+        @AfterClass
+        public static void afterSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+
+        @Test
+        public void test()
+        {
+            t = Thread.currentThread();
+            assertThat( t.getName(), is( "maven-surefire-plugin@NotThreadSafe" ) );
+        }
+    }
+
+    @NotThreadSafe
+    public static class NotThreadSafeTest3
+    {
+        static volatile Thread t;
+
+        @Test
+        public void test()
+        {
+            t = Thread.currentThread();
+            assertThat( t.getName(), is( "maven-surefire-plugin@NotThreadSafe" ) );
+        }
+    }
+
+    @RunWith( Suite.class )
+    @Suite.SuiteClasses( { NormalTest1.class, NormalTest2.class } )
+    @NotThreadSafe
+    public static class NotThreadSafeTestSuite
     {
+        @BeforeClass
+        public static void beforeSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+
+        @AfterClass
+        public static void afterSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+    }
+
+    public static class NormalTest1
+    {
+        static volatile Thread t;
+
+        @Test
+        public void test()
+        {
+            t = Thread.currentThread();
+        }
+    }
+
+    public static class NormalTest2
+    {
+        static volatile Thread t;
+
+        @Test
+        public void test()
+        {
+            t = Thread.currentThread();
+        }
+    }
+
+    @RunWith( Suite.class )
+    @Suite.SuiteClasses( { NotThreadSafeTest1.class, NormalTest1.class } )
+    public static class MixedSuite
+    {
+        @BeforeClass
+        public static void beforeSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+
+        @AfterClass
+        public static void afterSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+    }
+
+    @RunWith( Suite.class )
+    @Suite.SuiteClasses( { NotThreadSafeTest3.class, NormalTest1.class } )
+    @NotThreadSafe
+    public static class OverMixedSuite
+    {
+        @BeforeClass
+        public static void beforeSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
+
+        @AfterClass
+        public static void afterSuite()
+        {
+            assertThat( Thread.currentThread().getName(), is( not( "maven-surefire-plugin@NotThreadSafe" ) ) );
+        }
     }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/7f2c1fb1/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java
----------------------------------------------------------------------
diff --git a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java
index 68e16e1..e094e11 100644
--- a/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java
+++ b/surefire-providers/surefire-junit47/src/test/java/org/apache/maven/surefire/junitcore/pc/ParallelComputerUtilTest.java
@@ -23,6 +23,7 @@ import org.apache.maven.surefire.junitcore.JUnitCoreParameters;
 import org.apache.maven.surefire.testset.TestSetFailedException;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.experimental.theories.DataPoint;
@@ -76,6 +77,12 @@ public final class ParallelComputerUtilTest
         ParallelComputerUtil.setDefaultAvailableProcessors();
     }
 
+    @Before
+    public void beforeTest()
+    {
+        assertFalse( Thread.currentThread().isInterrupted() );
+    }
+
     private static Properties parallel( String parallel )
     {
         Properties properties = new Properties();
@@ -989,9 +996,10 @@ public final class ParallelComputerUtilTest
         long deltaTime = 500L;
 
         assertEquals( 2500L, timeSpent, deltaTime );
-        assertTrue( pc.describeElapsedTimeout().contains(
-            "The test run has finished abruptly after timeout of 2.5 seconds." ) );
-        assertTrue( pc.describeElapsedTimeout().contains( TestClass.class.getName() ) );
+        String description = pc.describeElapsedTimeout();
+        assertTrue( description.contains( "The test run has finished abruptly after timeout of 2.5 seconds.") );
+        assertTrue( description.contains( "These tests were executed in prior to the shutdown operation:\n"
+                + TestClass.class.getName() ) );
     }
 
     @Test
@@ -1011,9 +1019,34 @@ public final class ParallelComputerUtilTest
         long deltaTime = 500L;
 
         assertEquals( 2500L, timeSpent, deltaTime );
-        assertTrue( pc.describeElapsedTimeout().contains(
-            "The test run has finished abruptly after timeout of 2.5 seconds." ) );
-        assertTrue( pc.describeElapsedTimeout().contains( TestClass.class.getName() ) );
+        String description = pc.describeElapsedTimeout();
+        assertTrue( description.contains( "The test run has finished abruptly after timeout of 2.5 seconds.") );
+        assertTrue( description.contains( "These tests were executed in prior to the shutdown operation:\n"
+                + TestClass.class.getName() ) );
+    }
+
+    @Test
+    public void timeoutAndForcedShutdown()
+        throws TestSetFailedException, ExecutionException, InterruptedException
+    {
+        // The JUnitCore returns after 2.5s and the test-methods in TestClass are interrupted after 3.5s.
+        Properties properties = new Properties();
+        properties.setProperty( PARALLEL_KEY, "methods" );
+        properties.setProperty( THREADCOUNTMETHODS_KEY, "2" );
+        properties.setProperty( PARALLEL_TIMEOUT_KEY, Double.toString( 2.5d ) );
+        properties.setProperty( PARALLEL_TIMEOUTFORCED_KEY, Double.toString( 3.5d ) );
+        JUnitCoreParameters params = new JUnitCoreParameters( properties );
+        ParallelComputerBuilder pcBuilder = new ParallelComputerBuilder( params );
+        ParallelComputer pc = pcBuilder.buildComputer();
+        new JUnitCore().run( pc, TestClass.class );
+        long timeSpent = runtime.stop();
+        long deltaTime = 500L;
+
+        assertEquals( 2500L, timeSpent, deltaTime );
+        String description = pc.describeElapsedTimeout();
+        assertTrue( description.contains( "The test run has finished abruptly after timeout of 2.5 seconds.") );
+        assertTrue( description.contains( "These tests were executed in prior to the shutdown operation:\n"
+                                              + TestClass.class.getName() ) );
     }
 
     public static class TestClass
@@ -1023,7 +1056,8 @@ public final class ParallelComputerUtilTest
             throws InterruptedException
         {
             long t1 = System.currentTimeMillis();
-            try{
+            try
+            {
                 Thread.sleep( 5000L );
             }
             finally
@@ -1037,7 +1071,8 @@ public final class ParallelComputerUtilTest
             throws InterruptedException
         {
             long t1 = System.currentTimeMillis();
-            try{
+            try
+            {
                 Thread.sleep( 5000L );
             }
             finally
@@ -1051,7 +1086,8 @@ public final class ParallelComputerUtilTest
             throws InterruptedException
         {
             long t1 = System.currentTimeMillis();
-            try{
+            try
+            {
                 Thread.sleep( 5000L );
             }
             finally


Mime
View raw message