[SUREFIRE-580] [SUREFIRE-524] new mechanism to dispatch commands to forked jvm


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

Branch: refs/heads/master
Commit: 78dca27ef03bd939e700d7b00babaac44e7d1707
Parents: 7907ade
Author: Tibor17 <tibo...@lycos.com>
Authored: Thu Jul 23 23:21:14 2015 +0200
Committer: Tibor17 <tibo...@lycos.com>
Committed: Thu Jul 23 23:28:19 2015 +0200

----------------------------------------------------------------------
 maven-surefire-common/pom.xml                   |   3 +
 .../surefire/booterclient/ForkStarter.java      |  22 +-
 .../TestProvidingInputStream.java               | 105 ++++----
 .../TestProvidingInputStreamTest.java           | 141 ++++++++++
 .../apache/maven/surefire/JUnit4SuiteTest.java  |  76 ++++++
 pom.xml                                         |  11 +
 .../apache/maven/surefire/booter/Command.java   |  50 ++++
 .../surefire/booter/MasterProcessCommand.java   | 199 +++++++++++++++
 .../surefire/testset/TestListResolver.java      |  15 +-
 .../surefire/util/internal/StringUtils.java     |  20 ++
 .../booter/MasterProcessCommandTest.java        | 155 +++++++++++
 surefire-booter/pom.xml                         |   1 +
 .../maven/surefire/booter/ForkedBooter.java     |   2 +-
 .../maven/surefire/booter/LazyTestsToRun.java   | 113 +-------
 .../surefire/booter/MasterProcessListener.java  |  28 ++
 .../surefire/booter/MasterProcessReader.java    | 255 +++++++++++++++++++
 .../maven/surefire/booter/JUnit4SuiteTest.java  |   1 +
 .../booter/MasterProcessReaderTest.java         | 193 ++++++++++++++
 .../surefire/booter/NewClassLoaderRunner.java   | 191 ++++++++++++++
 .../maven/surefire/junit/JUnit3Provider.java    |   1 -
 .../maven/surefire/junit4/JUnit4Provider.java   |   8 +-
 21 files changed, 1413 insertions(+), 177 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/maven-surefire-common/pom.xml
----------------------------------------------------------------------
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index b49798a..c6303e0 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -119,6 +119,9 @@
         <artifactId>maven-surefire-plugin</artifactId>
         <configuration>
           <redirectTestOutputToFile>true</redirectTestOutputToFile>
+          <includes>
+            <include>**/JUnit4SuiteTest.java</include>
+          </includes>
         </configuration>
         <dependencies>
           <dependency>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
index 804a08e..4b2e184 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java
@@ -35,6 +35,7 @@ import org.apache.maven.shared.utils.cli.CommandLineUtils;
 import org.apache.maven.shared.utils.cli.ShutdownHookUtils;
 import org.apache.maven.surefire.booter.Classpath;
 import org.apache.maven.surefire.booter.ClasspathConfiguration;
+import org.apache.maven.surefire.booter.Command;
 import org.apache.maven.surefire.booter.KeyValueSource;
 import org.apache.maven.surefire.booter.PropertiesWrapper;
 import org.apache.maven.surefire.booter.ProviderConfiguration;
@@ -72,6 +73,8 @@ import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.atomic.AtomicReference;
 
 import static org.apache.maven.surefire.booter.Classpath.join;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
+import static java.lang.StrictMath.min;
 
 /**
  * Starts the fork or runs in-process.
@@ -210,18 +213,17 @@ public class ForkStarter
         ThreadPoolExecutor executorService = new ThreadPoolExecutor( 
forkCount, forkCount, 60, TimeUnit.SECONDS,
                                                                   new 
ArrayBlockingQueue<Runnable>( forkCount ) );
         executorService.setThreadFactory( threadFactory );
-        Collection<TestProvidingInputStream> testStreams = new 
ArrayList<TestProvidingInputStream>();
         try
         {
-            final Queue<String> messageQueue = new 
ConcurrentLinkedQueue<String>();
+            final Queue<Command> commands = new 
ConcurrentLinkedQueue<Command>();
             for ( Class<?> clazz : getSuitesIterator() )
             {
-                messageQueue.add( clazz.getName() );
+                commands.add( new Command( RUN_CLASS, clazz.getName() ) );
             }
-
-            for ( int forkNum = 0, total = messageQueue.size(); forkNum < 
forkCount && forkNum < total; forkNum++ )
+            Collection<TestProvidingInputStream> testStreams = new 
ArrayList<TestProvidingInputStream>();
+            for ( int forkNum = 0, total = min( forkCount, commands.size() ); 
forkNum < total; forkNum++ )
             {
-                final TestProvidingInputStream testProvidingInputStream = new 
TestProvidingInputStream( messageQueue );
+                final TestProvidingInputStream testProvidingInputStream = new 
TestProvidingInputStream( commands );
                 testStreams.add( testProvidingInputStream );
                 Callable<RunResult> pf = new Callable<RunResult>()
                 {
@@ -241,11 +243,11 @@ public class ForkStarter
                 };
                 results.add( executorService.submit( pf ) );
             }
+            dispatchTestSetFinished( testStreams );
             return awaitResultsDone( results, executorService );
         }
         finally
         {
-            closeTestStreams( testStreams );
             closeExecutor( executorService );
         }
     }
@@ -318,11 +320,11 @@ public class ForkStarter
         return globalResult;
     }
 
-    private static void closeTestStreams( Iterable<TestProvidingInputStream> 
testStreams )
+    private static void dispatchTestSetFinished( 
Iterable<TestProvidingInputStream> testStreams )
     {
-        for ( TestProvidingInputStream testStream: testStreams )
+        for ( TestProvidingInputStream testStream : testStreams )
         {
-            testStream.close();
+            testStream.testSetFinished();
         }
     }
 

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
index 31ca4d7..97bf24d 100644
--- 
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
+++ 
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStream.java
@@ -19,14 +19,17 @@ package 
org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
  * under the License.
  */
 
-import java.io.EOFException;
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Queue;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-import static 
org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
+import static 
org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED;
+import static 
org.apache.maven.surefire.util.internal.StringUtils.requireNonNull;
 
 /**
  * An {@link InputStream} that, when read, provides test class names out of a 
queue.
@@ -45,7 +48,7 @@ public class TestProvidingInputStream
 {
     private final Semaphore semaphore = new Semaphore( 0 );
 
-    private final Queue<String> testItemQueue;
+    private final Queue<Command> commands;
 
     private final AtomicBoolean closed = new AtomicBoolean();
 
@@ -53,16 +56,18 @@ public class TestProvidingInputStream
 
     private int currentPos;
 
+    private MasterProcessCommand lastCommand;
+
     private volatile FlushReceiverProvider flushReceiverProvider;
 
     /**
      * C'tor
      *
-     * @param testItemQueue source of the tests to be read from this stream
+     * @param commands source of the tests to be read from this stream
      */
-    public TestProvidingInputStream( Queue<String> testItemQueue )
+    public TestProvidingInputStream( Queue<Command> commands )
     {
-        this.testItemQueue = testItemQueue;
+        this.commands = commands;
     }
 
     /**
@@ -70,7 +75,12 @@ public class TestProvidingInputStream
      */
     public void setFlushReceiverProvider( FlushReceiverProvider 
flushReceiverProvider )
     {
-        this.flushReceiverProvider = flushReceiverProvider;
+        this.flushReceiverProvider = requireNonNull( flushReceiverProvider );
+    }
+
+    public void testSetFinished()
+    {
+        commands.add( new Command( TEST_SET_FINISHED ) );
     }
 
     /**
@@ -84,63 +94,45 @@ public class TestProvidingInputStream
     public int read()
         throws IOException
     {
-        if ( closed.get() )
-        {
-            // help GC to free this object because StreamFeeder Thread cannot 
read it after IOE
-            currentBuffer = null;
-            throw new EOFException( "closed unexpectedly" );
-        }
-        else
+        byte[] buffer = currentBuffer;
+        if ( buffer == null )
         {
-            // isolation of instance variable in Thread stack
-            byte[] buffer = currentBuffer;
-
-            if ( buffer == null )
+            if ( flushReceiverProvider != null )
             {
-                if ( flushReceiverProvider != null )
-                {
-                    FlushReceiver flushing = 
flushReceiverProvider.getFlushReceiver();
-                    if ( flushing != null )
-                    {
-                        flushing.flush();
-                    }
-                }
-
-                awaitNextTest();
-
-                if ( closed.get() )
+                FlushReceiver flushReceiver = 
flushReceiverProvider.getFlushReceiver();
+                if ( flushReceiver != null )
                 {
-                    // help GC to free this object because StreamFeeder Thread 
cannot read it after IOE
-                    currentBuffer = null;
-                    throw new EOFException( "closed unexpectedly" );
-                }
-
-                String currentElement = testItemQueue.poll();
-                if ( currentElement != null )
-                {
-                    buffer = encodeStringForForkCommunication( currentElement 
);
-                    // may override NULL from close(), therefore setting 
explicitly to NULL if IOE elsewhere
-                    currentBuffer = buffer;
-                    currentPos = 0;
-                }
-                else
-                {
-                    // help GC to free this object since it's not needed as 
there's no new test
-                    currentBuffer = null;
-                    return -1;
+                    flushReceiver.flush();
                 }
             }
 
-            if ( currentPos < buffer.length )
+            if ( lastCommand == TEST_SET_FINISHED || closed.get() )
             {
-                return buffer[currentPos++] & 0xff;
+                close();
+                return -1;
             }
-            else
+
+            awaitNextTest();
+
+            if ( closed.get() )
             {
-                currentBuffer = null;
-                return '\n' & 0xff;
+                return -1;
             }
+
+            Command command = commands.poll();
+            lastCommand = command.getCommandType();
+            String test = command.getData();
+            buffer = lastCommand == TEST_SET_FINISHED ? lastCommand.encode() : 
lastCommand.encode( test );
+        }
+
+        int b =  buffer[currentPos++] & 0xff;
+        if ( currentPos == buffer.length )
+        {
+            buffer = null;
+            currentPos = 0;
         }
+        currentBuffer = buffer;
+        return b;
     }
 
     private void awaitNextTest()
@@ -175,11 +167,8 @@ public class TestProvidingInputStream
         if ( closed.compareAndSet( false, true ) )
         {
             currentBuffer = null;
-            int permits = semaphore.drainPermits();
-            if ( permits == 0 )
-            {
-                semaphore.release();
-            }
+            semaphore.drainPermits();
+            semaphore.release();
         }
     }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
new file mode 100644
index 0000000..feca48b
--- /dev/null
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestProvidingInputStreamTest.java
@@ -0,0 +1,141 @@
+package org.apache.maven.plugin.surefire.booterclient.lazytestprovider;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.booter.Command;
+import org.apache.maven.surefire.booter.MasterProcessCommand;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Queue;
+import java.util.concurrent.Callable;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * Asserts that this stream properly reads bytes from queue.
+ *
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.19
+ */
+public class TestProvidingInputStreamTest
+{
+    @Test
+    public void closedStreamShouldReturnEndOfStream()
+        throws IOException
+    {
+        Queue<Command> commands = new ArrayDeque<Command>();
+        TestProvidingInputStream is = new TestProvidingInputStream( commands );
+        is.close();
+        assertThat( is.read(), is( -1 ) );
+    }
+
+    @Test
+    public void emptyStreamShouldWaitUntilClosed()
+        throws Exception
+    {
+        Queue<Command> commands = new ArrayDeque<Command>();
+        final TestProvidingInputStream is = new TestProvidingInputStream( 
commands );
+        final Thread streamThread = Thread.currentThread();
+        FutureTask<Thread.State> futureTask = new FutureTask<Thread.State>( 
new Callable<Thread.State>()
+        {
+            public Thread.State call()
+            {
+                sleep( 1000 );
+                Thread.State state = streamThread.getState();
+                is.close();
+                return state;
+            }
+        } );
+        Thread assertionThread = new Thread( futureTask );
+        assertionThread.start();
+        assertThat( is.read(), is( -1 ) );
+        Thread.State state = futureTask.get();
+        assertThat( state, is( Thread.State.WAITING ) );
+    }
+
+    @Test
+    public void finishedTestsetShouldNotBlock()
+        throws IOException
+    {
+        Queue<Command> commands = new ArrayDeque<Command>();
+        commands.add( new Command( MasterProcessCommand.TEST_SET_FINISHED ) );
+        final TestProvidingInputStream is = new TestProvidingInputStream( 
commands );
+        new Thread( new Runnable()
+        {
+            public void run()
+            {
+                is.provideNewTest();
+            }
+        } ).start();
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 1 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( -1 ) );
+    }
+
+    @Test
+    public void shouldReadTest()
+        throws IOException
+    {
+        Queue<Command> commands = new ArrayDeque<Command>();
+        commands.add( new Command( MasterProcessCommand.RUN_CLASS, "Test" ) );
+        final TestProvidingInputStream is = new TestProvidingInputStream( 
commands );
+        new Thread( new Runnable()
+        {
+            public void run()
+            {
+                is.provideNewTest();
+            }
+        } ).start();
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 0 ) );
+        assertThat( is.read(), is( 4 ) );
+        assertThat( is.read(), is( (int) 'T' ) );
+        assertThat( is.read(), is( (int) 'e' ) );
+        assertThat( is.read(), is( (int) 's' ) );
+        assertThat( is.read(), is( (int) 't' ) );
+    }
+
+    private static void sleep( long millis )
+    {
+        try
+        {
+            TimeUnit.MILLISECONDS.sleep( millis );
+        }
+        catch ( InterruptedException e )
+        {
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
----------------------------------------------------------------------
diff --git 
a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
 
b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
new file mode 100644
index 0000000..995e2a1
--- /dev/null
+++ 
b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -0,0 +1,76 @@
+package org.apache.maven.surefire;
+
+/*
+ * 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.plugin.surefire.SurefirePropertiesTest;
+import 
org.apache.maven.plugin.surefire.booterclient.BooterDeserializerProviderConfigurationTest;
+import 
org.apache.maven.plugin.surefire.booterclient.BooterDeserializerStartupConfigurationTest;
+import org.apache.maven.plugin.surefire.booterclient.ForkConfigurationTest;
+import org.apache.maven.plugin.surefire.booterclient.ForkingRunListenerTest;
+import 
org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest;
+import org.apache.maven.plugin.surefire.report.DefaultReporterFactoryTest;
+import org.apache.maven.plugin.surefire.report.StatelessXmlReporterTest;
+import org.apache.maven.plugin.surefire.report.WrappedReportEntryTest;
+import org.apache.maven.plugin.surefire.runorder.RunEntryStatisticsMapTest;
+import org.apache.maven.plugin.surefire.util.DependenciesScannerTest;
+import org.apache.maven.plugin.surefire.util.DirectoryScannerTest;
+import org.apache.maven.plugin.surefire.util.SpecificFileFilterTest;
+import org.apache.maven.surefire.report.ConsoleOutputFileReporterTest;
+import org.apache.maven.surefire.report.FileReporterTest;
+import org.apache.maven.surefire.report.RunStatisticsTest;
+import org.apache.maven.surefire.util.RelocatorTest;
+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.19
+ */
+@Suite.SuiteClasses( {
+    RelocatorTest.class,
+    RunStatisticsTest.class,
+    FileReporterTest.class,
+    ConsoleOutputFileReporterTest.class,
+    SurefirePropertiesTest.class,
+    SpecificFileFilterTest.class,
+    DirectoryScannerTest.class,
+    DependenciesScannerTest.class,
+    RunEntryStatisticsMapTest.class,
+    WrappedReportEntryTest.class,
+    StatelessXmlReporterTest.class,
+    DefaultReporterFactoryTest.class,
+    ForkingRunListenerTest.class,
+    ForkConfigurationTest.class,
+    BooterDeserializerStartupConfigurationTest.class,
+    BooterDeserializerProviderConfigurationTest.class,
+    TestProvidingInputStreamTest.class,
+} )
+@RunWith( Suite.class )
+public class JUnit4SuiteTest
+{
+    public static Test suite()
+    {
+        return new JUnit4TestAdapter( JUnit4SuiteTest.class );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/pom.xml
----------------------------------------------------------------------
diff --git a/pom.xml b/pom.xml
index c802a4f..5eb15e3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -226,6 +226,12 @@
         <scope>test</scope>
       </dependency>
       <dependency>
+        <groupId>org.hamcrest</groupId>
+        <artifactId>hamcrest-library</artifactId>
+        <version>1.3</version>
+        <scope>test</scope>
+      </dependency>
+      <dependency>
         <groupId>com.google.code.findbugs</groupId>
         <artifactId>jsr305</artifactId>
         <version>2.0.1</version>
@@ -239,6 +245,11 @@
       <artifactId>junit</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>org.hamcrest</groupId>
+      <artifactId>hamcrest-library</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
----------------------------------------------------------------------
diff --git 
a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java 
b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
new file mode 100644
index 0000000..992c78c
--- /dev/null
+++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Command.java
@@ -0,0 +1,50 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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.
+ */
+
+/**
+ * Encapsulates data and command sent from master to forked process.
+ */
+public final class Command
+{
+    private final MasterProcessCommand command;
+    private final String data;
+
+    public Command( MasterProcessCommand command, String data )
+    {
+        this.command = command;
+        this.data = data;
+    }
+
+    public Command( MasterProcessCommand command )
+    {
+        this( command, null );
+    }
+
+    public MasterProcessCommand getCommandType()
+    {
+        return command;
+    }
+
+    public String getData()
+    {
+        return data;
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
----------------------------------------------------------------------
diff --git 
a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
 
b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
new file mode 100644
index 0000000..0a553f7
--- /dev/null
+++ 
b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessCommand.java
@@ -0,0 +1,199 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+
+import static 
org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
+import static 
org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
+import static 
org.apache.maven.surefire.util.internal.StringUtils.requireNonNull;
+import static java.lang.String.format;
+
+/**
+ * Commands which are sent from plugin to the forked jvm.
+ * Support and methods related to the commands.
+ *
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.19
+ */
+public enum MasterProcessCommand
+{
+    RUN_CLASS( 0, String.class ),
+    TEST_SET_FINISHED( 1, Void.class ),
+    STOP_ON_NEXT_TEST( 2, Void.class ),
+    SHUTDOWN( 3, Void.class ),
+    /** To tell a forked process that the master process is still alive. 
Repeated after 30 seconds. */
+    NOOP( 4, Void.class );
+
+    private final int id;
+
+    private final Class<?> dataType;
+
+    MasterProcessCommand( int id, Class<?> dataType )
+    {
+        this.id = id;
+        this.dataType = requireNonNull( dataType, "dataType cannot be null" );
+    }
+
+    public int getId()
+    {
+        return id;
+    }
+
+    public Class<?> getDataType()
+    {
+        return dataType;
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    public byte[] encode( String data )
+    {
+        if ( getDataType() != String.class )
+        {
+            throw new IllegalArgumentException( "Data type can be only " + 
getDataType() );
+        }
+        byte[] dataBytes = fromDataType( data );
+        byte[] encoded = new byte[8 + dataBytes.length];
+        int command = getId();
+        int len = dataBytes.length;
+        setCommandAndDataLength( command, len, encoded );
+        System.arraycopy( dataBytes, 0, encoded, 8, dataBytes.length );
+        return encoded;
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    public byte[] encode()
+    {
+        if ( getDataType() != Void.class )
+        {
+            throw new IllegalArgumentException( "Data type can be only " + 
getDataType() );
+        }
+        byte[] encoded = new byte[8];
+        int command = getId();
+        setCommandAndDataLength( command, 0, encoded );
+        return encoded;
+    }
+
+    public static Command decode( DataInputStream is )
+        throws IOException
+    {
+        MasterProcessCommand command = resolve( is.readInt() );
+        if ( command == null )
+        {
+            return null;
+        }
+        else
+        {
+            int dataLength = is.readInt();
+            if ( dataLength > 0 )
+            {
+                byte[] buffer = new byte[dataLength];
+                int read = 0;
+                int total = 0;
+                do
+                {
+                    total += read;
+                    read = is.read( buffer, total, dataLength - total );
+                } while ( read > 0 );
+
+                if ( command.getDataType() == Void.class )
+                {
+                    // must read entire sequence to get to the next command; 
cannot be above the loop
+                    throw new IOException( "Command " + command + " read Void 
data with length " + dataLength );
+                }
+
+                if ( total != dataLength )
+                {
+                    if ( read == -1 )
+                    {
+                        throw new EOFException( "stream closed" );
+                    }
+
+                    throw new EOFException( format( "%s read %d out of %d 
bytes",
+                                                    
MasterProcessCommand.class, total, dataLength ) );
+                }
+
+                String data = command.toDataTypeAsString( buffer );
+                return new Command( command, data );
+            }
+            else
+            {
+                return new Command( command );
+            }
+        }
+    }
+
+    String toDataTypeAsString( byte... data )
+    {
+        try
+        {
+            switch ( this )
+            {
+                case RUN_CLASS:
+                    return new String( data, FORK_STREAM_CHARSET_NAME );
+                default:
+                    return null;
+            }
+        }
+        catch ( UnsupportedEncodingException e )
+        {
+            throw new IllegalStateException( e );
+        }
+    }
+
+    byte[] fromDataType( String data )
+    {
+        switch ( this )
+        {
+            case RUN_CLASS:
+                return encodeStringForForkCommunication( data );
+            default:
+                return new byte[0];
+        }
+    }
+
+    static MasterProcessCommand resolve( int id )
+    {
+        for ( MasterProcessCommand command : values() )
+        {
+            if ( id == command.id )
+            {
+                return command;
+            }
+        }
+        return null;
+    }
+
+    @SuppressWarnings( "checkstyle:magicnumber" )
+    static void setCommandAndDataLength( int command, int dataLength, byte... 
encoded )
+    {
+        encoded[0] = (byte) ( command >>> 24 );
+        encoded[1] = (byte) ( command >>> 16 );
+        encoded[2] = (byte) ( command >>> 8 );
+        encoded[3] = (byte) command;
+        encoded[4] = (byte) ( dataLength >>> 24 );
+        encoded[5] = (byte) ( dataLength >>> 16 );
+        encoded[6] = (byte) ( dataLength >>> 8 );
+        encoded[7] = (byte) dataLength;
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
----------------------------------------------------------------------
diff --git 
a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
 
b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
index b233147..6bda2ad 100644
--- 
a/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
+++ 
b/surefire-api/src/main/java/org/apache/maven/surefire/testset/TestListResolver.java
@@ -180,8 +180,7 @@ public class TestListResolver
 
     public boolean shouldRun( Class<?> testClass, String methodName )
     {
-        String clsFile = testClass == null ? null : 
testClass.getName().replace( '.', '/' ) + JAVA_CLASS_FILE_EXTENSION;
-        return shouldRun( clsFile, methodName );
+        return shouldRun( toClassFileName( testClass ), methodName );
     }
 
     public boolean shouldRun( String testClassFile, String methodName )
@@ -285,6 +284,18 @@ public class TestListResolver
         return getPluginParameterTest();
     }
 
+    public static String toClassFileName( Class<?> test )
+    {
+        return test == null ? null : toClassFileName( test.getName() );
+    }
+
+    public static String toClassFileName( String fullyQualifiedTestClass )
+    {
+        return fullyQualifiedTestClass == null
+            ? null
+            : fullyQualifiedTestClass.replace( '.', '/' ) + 
JAVA_CLASS_FILE_EXTENSION;
+    }
+
     static String removeExclamationMark( String s )
     {
         return s.length() != 0 && s.charAt( 0 ) == '!' ? s.substring( 1 ) : s;

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
----------------------------------------------------------------------
diff --git 
a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
 
b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
index 8158fb7..043b87b 100644
--- 
a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
+++ 
b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/StringUtils.java
@@ -350,4 +350,24 @@ public class StringUtils
            throw new RuntimeException( "The JVM must support Charset " + 
FORK_STREAM_CHARSET_NAME, e );
         }
     }
+
+    /*
+    * In JDK7 use java.util.Objects instead.
+    * */
+    public static <T> T requireNonNull( T obj, String message )
+    {
+        if ( obj == null )
+        {
+            throw new NullPointerException( message );
+        }
+        return obj;
+    }
+
+    /*
+    * In JDK7 use java.util.Objects instead.
+    * */
+    public static <T> T requireNonNull( T obj )
+    {
+        return requireNonNull( obj, null );
+    }
 }

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
----------------------------------------------------------------------
diff --git 
a/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
 
b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
new file mode 100644
index 0000000..00e7bbc
--- /dev/null
+++ 
b/surefire-api/src/test/java/org/apache/maven/surefire/booter/MasterProcessCommandTest.java
@@ -0,0 +1,155 @@
+package org.apache.maven.surefire.booter;
+/*
+ * 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.TestCase;
+import org.junit.Rule;
+import org.junit.rules.ExpectedException;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+import static org.apache.maven.surefire.booter.MasterProcessCommand.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.19
+ */
+public class MasterProcessCommandTest
+    extends TestCase
+{
+    @Rule
+    public final ExpectedException exception = ExpectedException.none();
+
+    public void testEncodedStreamSequence()
+    {
+        byte[] streamSequence = new byte[10];
+        streamSequence[8] = (byte) 'T';
+        streamSequence[9] = (byte) 'e';
+        setCommandAndDataLength( 256, 2, streamSequence );
+        assertEquals( streamSequence[0], (byte) 0 );
+        assertEquals( streamSequence[1], (byte) 0 );
+        assertEquals( streamSequence[2], (byte) 1 );
+        assertEquals( streamSequence[3], (byte) 0 );
+        assertEquals( streamSequence[4], (byte) 0 );
+        assertEquals( streamSequence[5], (byte) 0 );
+        assertEquals( streamSequence[6], (byte) 0 );
+        assertEquals( streamSequence[7], (byte) 2 );
+        // remain unchanged
+        assertEquals( streamSequence[8], (byte) 'T' );
+        assertEquals( streamSequence[9], (byte) 'e' );
+    }
+
+    public void testResolved()
+    {
+        for ( MasterProcessCommand command : MasterProcessCommand.values() )
+        {
+            assertThat( command, is( resolve( command.getId() ) ) );
+        }
+    }
+
+    public void testDataToByteArrayAndBack()
+    {
+        String dummyData = "pkg.Test";
+        for ( MasterProcessCommand command : MasterProcessCommand.values() )
+        {
+            switch ( command )
+            {
+                case RUN_CLASS:
+                    assertEquals( String.class, command.getDataType() );
+                    byte[] encoded = command.fromDataType( dummyData );
+                    assertThat( encoded.length, is( 8 ) );
+                    assertThat( encoded[0], is( (byte) 'p' ) );
+                    assertThat( encoded[1], is( (byte) 'k' ) );
+                    assertThat( encoded[2], is( (byte) 'g' ) );
+                    assertThat( encoded[3], is( (byte) '.' ) );
+                    assertThat( encoded[4], is( (byte) 'T' ) );
+                    assertThat( encoded[5], is( (byte) 'e' ) );
+                    assertThat( encoded[6], is( (byte) 's' ) );
+                    assertThat( encoded[7], is( (byte) 't' ) );
+                    String decoded = command.toDataTypeAsString( encoded );
+                    assertThat( decoded, is( dummyData ) );
+                    break;
+                case TEST_SET_FINISHED:
+                    assertEquals( Void.class, command.getDataType() );
+                    encoded = command.fromDataType( dummyData );
+                    assertThat( encoded.length, is( 0 ) );
+                    decoded = command.toDataTypeAsString( encoded );
+                    assertNull( decoded );
+                    break;
+                case STOP_ON_NEXT_TEST:
+                    assertEquals( Void.class, command.getDataType() );
+                    encoded = command.fromDataType( dummyData );
+                    assertThat( encoded.length, is( 0 ) );
+                    decoded = command.toDataTypeAsString( encoded );
+                    assertNull( decoded );
+                    break;
+                case SHUTDOWN:
+                    assertEquals( Void.class, command.getDataType() );
+                    encoded = command.fromDataType( dummyData );
+                    assertThat( encoded.length, is( 0 ) );
+                    decoded = command.toDataTypeAsString( encoded );
+                    assertNull( decoded );
+                    break;
+                case NOOP:
+                    assertEquals( Void.class, command.getDataType() );
+                    encoded = command.fromDataType( dummyData );
+                    assertThat( encoded.length, is( 0 ) );
+                    decoded = command.toDataTypeAsString( encoded );
+                    assertNull( decoded );
+                    break;
+                default:
+                    fail();
+            }
+            assertThat( command, is( resolve( command.getId() ) ) );
+        }
+    }
+
+    public void testEncodedDecodedIsSameForRunClass()
+        throws IOException
+    {
+        byte[] encoded = RUN_CLASS.encode( "pkg.Test" );
+        assertThat( encoded.length, is( 16 ) );
+        assertThat( encoded[0], is( (byte) 0 ) );
+        assertThat( encoded[1], is( (byte) 0 ) );
+        assertThat( encoded[2], is( (byte) 0 ) );
+        assertThat( encoded[3], is( (byte) 0 ) );
+        assertThat( encoded[4], is( (byte) 0 ) );
+        assertThat( encoded[5], is( (byte) 0 ) );
+        assertThat( encoded[6], is( (byte) 0 ) );
+        assertThat( encoded[7], is( (byte) 8 ) );
+        assertThat( encoded[8], is( (byte) 'p' ) );
+        assertThat( encoded[9], is( (byte) 'k' ) );
+        assertThat( encoded[10], is( (byte) 'g' ) );
+        assertThat( encoded[11], is( (byte) '.' ) );
+        assertThat( encoded[12], is( (byte) 'T' ) );
+        assertThat( encoded[13], is( (byte) 'e' ) );
+        assertThat( encoded[14], is( (byte) 's' ) );
+        assertThat( encoded[15], is( (byte) 't' ) );
+        Command command = decode( new DataInputStream( new 
ByteArrayInputStream( encoded ) ) );
+        assertNotNull( command );
+        assertThat( command.getCommandType(), is( RUN_CLASS ) );
+        assertThat( command.getData(), is( "pkg.Test" ) );
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/pom.xml
----------------------------------------------------------------------
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index 50598bf..bf37966 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -50,6 +50,7 @@
           </dependency>
         </dependencies>
         <configuration>
+          <redirectTestOutputToFile>true</redirectTestOutputToFile>
           <includes>
             <include>**/JUnit4SuiteTest.java</include>
           </includes>

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
index ca5dd26..518c523 100644
--- 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java
@@ -98,7 +98,7 @@ public final class ForkedBooter
             }
             else if ( readTestsFromInputStream )
             {
-                testSet = new LazyTestsToRun( System.in, originalOut );
+                testSet = new LazyTestsToRun( originalOut );
             }
             else
             {

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
index a5611c0..d3e770c 100644
--- 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/LazyTestsToRun.java
@@ -19,135 +19,55 @@ package org.apache.maven.surefire.booter;
  * under the License.
  */
 
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
 import java.io.PrintStream;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
-import java.util.List;
 
 import org.apache.maven.surefire.util.ReflectionUtils;
 import org.apache.maven.surefire.util.TestsToRun;
 
-// CHECKSTYLE_OFF: imports
-import static 
org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
-import static 
org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
-
 /**
  * A variant of TestsToRun that is provided with test class names
- * from an {@link InputStream} (e.g. {@code System.in}). The method
- * {@link #iterator()} returns an Iterator that blocks on calls to
- * {@link Iterator#hasNext()} until new classes are available, or no more
- * classes will be available.
+ * from an {@code System.in}.
+ * The method {@link #iterator()} returns an Iterator that blocks on calls to
+ * {@link Iterator#hasNext()} or {@link Iterator#next()} until new classes are 
available, or no more
+ * classes will be available or the internal stream is closed.
  *
  * @author Andreas Gudian
+ * @author Tibor Digana
  */
 final class LazyTestsToRun
     extends TestsToRun
 {
-    private final List<Class<?>> workQueue = new ArrayList<Class<?>>();
-
-    private final BufferedReader inputReader;
-
     private final PrintStream originalOutStream;
 
-    private boolean streamClosed;
-
     /**
      * C'tor
      *
-     * @param testSource        source to read the tests from
      * @param originalOutStream the output stream to use when requesting new 
new tests
      */
-    public LazyTestsToRun( InputStream testSource, PrintStream 
originalOutStream )
+    LazyTestsToRun( PrintStream originalOutStream )
     {
-        super( Collections.<Class<?>>emptyList() );
+        super( Collections.<Class<?>>emptySet() );
 
         this.originalOutStream = originalOutStream;
-
-        try
-        {
-            inputReader = new BufferedReader( new InputStreamReader( 
testSource, FORK_STREAM_CHARSET_NAME ) );
-        }
-        catch ( UnsupportedEncodingException e )
-        {
-            throw new RuntimeException( "The JVM must support Charset " + 
FORK_STREAM_CHARSET_NAME, e );
-        }
-    }
-
-    private void addWorkItem( String className )
-    {
-        synchronized ( workQueue )
-        {
-            workQueue.add( ReflectionUtils.loadClass( 
Thread.currentThread().getContextClassLoader(), className ) );
-        }
-    }
-
-    private void requestNextTest()
-    {
-        byte[] encoded =
-            encodeStringForForkCommunication( ( (char) 
ForkingRunListener.BOOTERCODE_NEXT_TEST ) + ",0,want more!\n" );
-        originalOutStream.write( encoded, 0, encoded.length );
     }
 
     private final class BlockingIterator
         implements Iterator<Class<?>>
     {
-        private int lastPos = -1;
+        private final Iterator<String> it =
+            MasterProcessReader.getReader().getIterableClasses( 
originalOutStream ).iterator();
 
         public boolean hasNext()
         {
-            int nextPos = lastPos + 1;
-            synchronized ( workQueue )
-            {
-                if ( workQueue.size() > nextPos )
-                {
-                    return true;
-                }
-                else
-                {
-                    if ( needsToWaitForInput( nextPos ) )
-                    {
-                        requestNextTest();
-                        try
-                        {
-                            String nextClassName = inputReader.readLine();
-                            if ( nextClassName == null )
-                            {
-                                streamClosed = true;
-                            }
-                            else
-                            {
-                                addWorkItem( nextClassName );
-                            }
-                        }
-                        catch ( IOException e )
-                        {
-                            streamClosed = true;
-                            return false;
-                        }
-                    }
-
-                    return workQueue.size() > nextPos;
-                }
-            }
-        }
-
-        private boolean needsToWaitForInput( int nextPos )
-        {
-            return workQueue.size() == nextPos && !streamClosed;
+            return it.hasNext();
         }
 
         public Class<?> next()
         {
-            synchronized ( workQueue )
-            {
-                return workQueue.get( ++lastPos );
-            }
+            String clazz = it.next();
+            return ReflectionUtils.loadClass( 
Thread.currentThread().getContextClassLoader(), clazz );
         }
 
         public void remove()
@@ -170,14 +90,7 @@ final class LazyTestsToRun
       */
     public String toString()
     {
-        StringBuilder sb = new StringBuilder( "LazyTestsToRun " );
-        synchronized ( workQueue )
-        {
-            sb.append( "(more items expected: " ).append( !streamClosed 
).append( "): " );
-            sb.append( workQueue );
-        }
-
-        return sb.toString();
+        return "LazyTestsToRun";
     }
 
     /* (non-Javadoc)

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java
 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java
new file mode 100644
index 0000000..89a4f89
--- /dev/null
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessListener.java
@@ -0,0 +1,28 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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.
+ */
+
+/**
+ * listener interface
+ */
+public interface MasterProcessListener
+{
+    void update( Command command );
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java
 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java
new file mode 100644
index 0000000..1d86167
--- /dev/null
+++ 
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java
@@ -0,0 +1,255 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.surefire.util.internal.DaemonThreadFactory;
+
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Queue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static java.lang.Thread.State.NEW;
+import static java.lang.Thread.State.RUNNABLE;
+import static java.lang.Thread.State.TERMINATED;
+import static 
org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS;
+import static org.apache.maven.surefire.booter.MasterProcessCommand.decode;
+import static 
org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication;
+import static org.apache.maven.surefire.util.internal.StringUtils.isNotBlank;
+import static org.apache.maven.surefire.util.internal.StringUtils.isBlank;
+import static 
org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_NEXT_TEST;
+
+/**
+ * Testing singleton {@code MasterProcessReader}.
+ *
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.19
+ */
+public final class MasterProcessReader
+{
+    private static final MasterProcessReader READER = new 
MasterProcessReader();
+
+    private final BlockingQueue<Command> classes = new 
LinkedBlockingQueue<Command>();
+
+    private final Queue<MasterProcessListener> listeners = new 
ConcurrentLinkedQueue<MasterProcessListener>();
+
+    private final Thread commandThread =
+        DaemonThreadFactory.newDaemonThread( new CommandRunnable(), 
"surefire-forkedjvm-command-thread" );
+
+    private final AtomicReference<Thread.State> state = new 
AtomicReference<Thread.State>( NEW );
+
+    public static MasterProcessReader getReader()
+    {
+        if ( READER.state.compareAndSet( NEW, RUNNABLE ) )
+        {
+            READER.commandThread.start();
+        }
+        return READER;
+    }
+
+    private MasterProcessReader()
+    {
+        commandThread.setDaemon( true );
+    }
+
+    Iterable<String> getIterableClasses( PrintStream originalOutStream )
+    {
+        return new ClassesIterable( originalOutStream );
+    }
+
+    void stop( boolean interruptCurrentThread )
+    {
+        if ( state.compareAndSet( NEW, TERMINATED ) || state.compareAndSet( 
RUNNABLE, TERMINATED ) )
+        {
+            classes.drainTo( new ArrayList<Command>() );
+            listeners.clear();
+            commandThread.interrupt();
+            if ( interruptCurrentThread )
+            {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    private final class ClassesIterable
+        implements Iterable<String>
+    {
+        private final ClassesIterator it;
+
+        public ClassesIterable( PrintStream originalOutStream )
+        {
+            it = new ClassesIterator( originalOutStream );
+        }
+
+        public Iterator<String> iterator()
+        {
+            return it;
+        }
+    }
+
+    private final class ClassesIterator
+        implements Iterator<String>
+    {
+        private final PrintStream originalOutStream;
+
+        private String clazz;
+
+        private ClassesIterator( PrintStream originalOutStream )
+        {
+            this.originalOutStream = originalOutStream;
+        }
+
+        public boolean hasNext()
+        {
+            popUnread();
+            return isNotBlank( clazz );
+        }
+
+        public String next()
+        {
+            popUnread();
+            try
+            {
+                if ( isBlank( clazz ) )
+                {
+                    throw new NoSuchElementException();
+                }
+                else
+                {
+                    return clazz;
+                }
+            }
+            finally
+            {
+                clazz = null;
+            }
+        }
+
+        public void remove()
+        {
+            throw new UnsupportedOperationException();
+        }
+
+        private void popUnread()
+        {
+            if ( state.get() == TERMINATED )
+            {
+                clazz = null;
+                return;
+            }
+
+            if ( isBlank( clazz ) )
+            {
+                requestNextTest();
+                try
+                {
+                    do
+                    {
+                        Command command = 
MasterProcessReader.this.classes.take();
+                        clazz = command.getCommandType() == TEST_SET_FINISHED 
? null : command.getData();
+                    }
+                    while ( tryNullWhiteClass() );
+                }
+                catch ( InterruptedException e )
+                {
+                    Thread.currentThread().interrupt();
+                    clazz = null;
+                }
+            }
+
+            if ( state.get() == TERMINATED )
+            {
+                clazz = null;
+            }
+        }
+
+        private boolean tryNullWhiteClass()
+        {
+            if ( clazz != null && isBlank( clazz ) )
+            {
+                clazz = null;
+                return true;
+            }
+            else
+            {
+                return false;
+            }
+        }
+
+        private void requestNextTest()
+        {
+            byte[] encoded = encodeStringForForkCommunication( ( (char) 
BOOTERCODE_NEXT_TEST ) + ",0,want more!\n" );
+            originalOutStream.write( encoded, 0, encoded.length );
+        }
+    }
+
+    private final class CommandRunnable
+        implements Runnable
+    {
+        public void run()
+        {
+            DataInputStream stdIn = new DataInputStream( System.in );
+            try
+            {
+                while ( MasterProcessReader.this.state.get() == RUNNABLE )
+                {
+                    Command command = decode( stdIn );
+                    if ( command != null )
+                    {
+                        // command is null if stream is corrupted, i.e. the 
first sequence could not be recognized
+                        insert( command );
+                    }
+                }
+            }
+            catch ( IOException e )
+            {
+                // ensure fail-safe iterator as well as safe to finish in 
for-each loop using ClassesIterator
+                insert( new Command( TEST_SET_FINISHED ) );
+                // and let us know what has happened with the stream
+                throw new IllegalStateException( e.getLocalizedMessage(), e );
+            }
+        }
+
+        @SuppressWarnings( "unchecked" )
+        private void insert( Command cmd )
+        {
+            MasterProcessCommand commandType = cmd.getCommandType();
+            if ( commandType == RUN_CLASS || commandType == TEST_SET_FINISHED )
+            {
+                MasterProcessReader.this.classes.add( cmd );
+            }
+            else
+            {
+                for ( MasterProcessListener listener : 
MasterProcessReader.this.listeners )
+                {
+                    listener.update( cmd );
+                }
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
index d426d27..5e84775 100644
--- 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java
@@ -32,6 +32,7 @@ import org.junit.runners.Suite;
  */
 @Suite.SuiteClasses( {
     ClasspathTest.class,
+    MasterProcessReaderTest.class,
     PropertiesWrapperTest.class,
     SurefireReflectorTest.class
 } )

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java
new file mode 100644
index 0000000..9440e0d
--- /dev/null
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/MasterProcessReaderTest.java
@@ -0,0 +1,193 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.ByteBuffer;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static 
org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.*;
+
+/**
+ * Testing singleton {@code MasterProcessReader} in multiple class loaders.
+ *
+ * @author <a href="mailto:tibordig...@apache.org";>Tibor Digana (tibor17)</a>
+ * @since 2.19
+ */
+@RunWith( NewClassLoaderRunner.class )
+public class MasterProcessReaderTest
+{
+    private final BlockingQueue<Byte> blockingStream = new 
LinkedBlockingQueue<Byte>();
+    private InputStream realInputStream;
+
+    @Before
+    public void init()
+        throws UnsupportedEncodingException
+    {
+        Thread.interrupted();
+        realInputStream = System.in;
+        addThisTestToPipeline( MasterProcessReaderTest.class.getName() );
+        System.setIn( new SystemInputStream() );
+    }
+
+    @After
+    public void deinit()
+    {
+        System.setIn( realInputStream );
+    }
+
+    @Test
+    public void readJustOneClass() throws Exception
+    {
+        MasterProcessReader reader = MasterProcessReader.getReader();
+        Iterator<String> it = reader.getIterableClasses( new PrintStream( new 
ByteArrayOutputStream() ) )
+            .iterator();
+        assertTrue( it.hasNext() );
+        assertThat( it.next(), is( getClass().getName() ) );
+        reader.stop( true );
+        assertFalse( it.hasNext() );
+        try
+        {
+            it.next();
+            fail();
+        }
+        catch ( NoSuchElementException e )
+        {
+            // expected
+        }
+    }
+
+    @Test( expected = NoSuchElementException.class )
+    public void stopBeforeReadInThread()
+        throws Throwable
+    {
+        final MasterProcessReader reader = MasterProcessReader.getReader();
+        Runnable runnable = new Runnable()
+        {
+            public void run()
+            {
+                Iterator<String> it = reader.getIterableClasses( new 
PrintStream( new ByteArrayOutputStream() ) )
+                    .iterator();
+                assertThat( it.next(), is( 
MasterProcessReaderTest.class.getName() ) );
+            }
+        };
+        FutureTask<Object> futureTask = new FutureTask<Object>( runnable, null 
);
+        Thread t = new Thread( futureTask );
+        reader.stop( false );
+        t.start();
+        try
+        {
+            futureTask.get();
+        }
+        catch ( ExecutionException e )
+        {
+            throw e.getCause();
+        }
+    }
+
+    @Test
+    public void readTwoClassesInThread()
+        throws Throwable
+    {
+        final MasterProcessReader reader = MasterProcessReader.getReader();
+        final CountDownLatch counter = new CountDownLatch( 1 );
+        Runnable runnable = new Runnable()
+        {
+            public void run()
+            {
+                Iterator<String> it = reader.getIterableClasses( new 
PrintStream( new ByteArrayOutputStream() ) )
+                    .iterator();
+                assertThat( it.next(), is( 
MasterProcessReaderTest.class.getName() ) );
+                counter.countDown();
+                assertThat( it.next(), is( 
PropertiesWrapperTest.class.getName() ) );
+            }
+        };
+        FutureTask<Object> futureTask = new FutureTask<Object>( runnable, null 
);
+        Thread t = new Thread( futureTask );
+        t.start();
+        counter.await();
+        addThisTestToPipeline( PropertiesWrapperTest.class.getName() );
+        try
+        {
+            futureTask.get();
+        }
+        catch ( ExecutionException e )
+        {
+            throw e.getCause();
+        }
+        finally
+        {
+            reader.stop( false );
+        }
+    }
+
+    private class SystemInputStream
+        extends InputStream
+    {
+        @Override
+        public int read()
+            throws IOException
+        {
+            try
+            {
+                return MasterProcessReaderTest.this.blockingStream.take();
+            }
+            catch ( InterruptedException e )
+            {
+                throw new IOException( e );
+            }
+        }
+    }
+
+    private void addThisTestToPipeline( String cls )
+        throws UnsupportedEncodingException
+    {
+        byte[] clazz = cls.getBytes( FORK_STREAM_CHARSET_NAME );
+        ByteBuffer buffer = ByteBuffer.allocate( 8 + clazz.length )
+            .putInt( MasterProcessCommand.RUN_CLASS.getId() )
+            .putInt( clazz.length )
+            .put( clazz );
+        buffer.rewind();
+        for ( ; buffer.hasRemaining(); )
+        {
+            blockingStream.add( buffer.get() );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
----------------------------------------------------------------------
diff --git 
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
new file mode 100644
index 0000000..4342ec7
--- /dev/null
+++ 
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/NewClassLoaderRunner.java
@@ -0,0 +1,191 @@
+package org.apache.maven.surefire.booter;
+
+/*
+ * 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.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.internal.runners.model.ReflectiveCallable;
+import org.junit.internal.runners.statements.ExpectException;
+import org.junit.internal.runners.statements.Fail;
+import org.junit.internal.runners.statements.RunAfters;
+import org.junit.internal.runners.statements.RunBefores;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.Statement;
+import org.junit.runners.model.TestClass;
+
+import java.lang.annotation.Annotation;
+import java.net.URLClassLoader;
+import java.util.List;
+
+/**
+ * JUnit runner testing methods in a separate class loader.
+ *
+ * @author Tibor Digana (tibor17)
+ * @since 2.19
+ */
+public class NewClassLoaderRunner
+    extends BlockJUnit4ClassRunner
+{
+    private Class<?> cls;
+
+    public NewClassLoaderRunner( Class<?> clazz )
+        throws InitializationError
+    {
+        super( clazz );
+    }
+
+    @Override
+    protected void runChild( FrameworkMethod method, RunNotifier notifier )
+    {
+        ClassLoader backup = Thread.currentThread().getContextClassLoader();
+        try
+        {
+            TestClassLoader loader = new TestClassLoader();
+            Thread.currentThread().setContextClassLoader( loader );
+            cls = getFromTestClassLoader( getTestClass().getName(), loader );
+            method = new FrameworkMethod( cls.getMethod( method.getName() ) );
+            super.runChild( method, notifier );
+        }
+        catch ( NoSuchMethodException e )
+        {
+            throw new IllegalStateException( e );
+        }
+        finally
+        {
+            Thread.currentThread().setContextClassLoader( backup );
+        }
+    }
+
+    @Override
+    protected Statement methodBlock( FrameworkMethod method )
+    {
+        try
+        {
+            Object test = new ReflectiveCallable()
+            {
+                @Override
+                protected Object runReflectiveCall()
+                    throws Throwable
+                {
+                    return createTest();
+                }
+            }.run();
+
+            Statement statement = methodInvoker( method, test );
+            statement = possiblyExpectingExceptions( method, test, statement );
+            statement = withBefores( method, test, statement );
+            statement = withAfters( method, test, statement );
+            return statement;
+        }
+        catch ( Throwable e )
+        {
+            return new Fail( e );
+        }
+    }
+
+    @Override
+    @SuppressWarnings( "unchecked" )
+    protected Statement possiblyExpectingExceptions( FrameworkMethod method, 
Object test, Statement next )
+    {
+        try
+        {
+            Class<? extends Annotation> t =
+                (Class<? extends Annotation>) 
Thread.currentThread().getContextClassLoader().loadClass(
+                    Test.class.getName() );
+            Annotation annotation = method.getAnnotation( t );
+            Class<? extends Throwable> exp =
+                (Class<? extends Throwable>) t.getMethod( "expected" ).invoke( 
annotation );
+            boolean isException = exp != null && 
!Test.None.class.getName().equals( exp.getName() );
+            return isException ? new ExpectException( next, exp ) : next;
+        }
+        catch ( Exception e )
+        {
+            throw new IllegalStateException( e );
+        }
+    }
+
+    @Override
+    @SuppressWarnings( "unchecked" )
+    protected Statement withBefores( FrameworkMethod method, Object target, 
Statement statement )
+    {
+        try
+        {
+            Class<? extends Annotation> before =
+                (Class<? extends Annotation>) 
Thread.currentThread().getContextClassLoader().loadClass(
+                    Before.class.getName() );
+            List<FrameworkMethod> befores = new TestClass( target.getClass() 
).getAnnotatedMethods( before );
+            return befores.isEmpty() ? statement : new RunBefores( statement, 
befores, target );
+        }
+        catch ( ClassNotFoundException e )
+        {
+            throw new IllegalStateException( e );
+        }
+    }
+
+    @Override
+    @SuppressWarnings( "unchecked" )
+    protected Statement withAfters( FrameworkMethod method, Object target, 
Statement statement )
+    {
+        try
+        {
+            Class<? extends Annotation> after =
+                (Class<? extends Annotation>) 
Thread.currentThread().getContextClassLoader().loadClass(
+                    After.class.getName() );
+            List<FrameworkMethod> afters = new TestClass( target.getClass() 
).getAnnotatedMethods( after );
+            return afters.isEmpty() ? statement : new RunAfters( statement, 
afters, target );
+        }
+        catch ( ClassNotFoundException e )
+        {
+            throw new IllegalStateException( e );
+        }
+    }
+
+    @Override
+    protected Object createTest()
+        throws Exception
+    {
+        return cls == null ? super.createTest() : 
cls.getConstructor().newInstance();
+    }
+
+    private static Class<?> getFromTestClassLoader( String clazz, 
TestClassLoader loader )
+    {
+        try
+        {
+            return Class.forName( clazz, true, loader );
+        }
+        catch ( ClassNotFoundException e )
+        {
+            throw new IllegalStateException( e );
+        }
+    }
+
+    public static class TestClassLoader
+        extends URLClassLoader
+    {
+        public TestClassLoader()
+        {
+            super( ( (URLClassLoader) 
Thread.currentThread().getContextClassLoader() ).getURLs(), null );
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
----------------------------------------------------------------------
diff --git 
a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
 
b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
index 470922f..e403a64 100644
--- 
a/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
+++ 
b/surefire-providers/surefire-junit3/src/main/java/org/apache/maven/surefire/junit/JUnit3Provider.java
@@ -115,7 +115,6 @@ public class JUnit3Provider
         return reflector.isJUnit3Available() && jUnit3TestChecker.accept( 
clazz )
             ? new JUnitTestSet( clazz, reflector )
             : new PojoTestSet( clazz );
-
     }
 
     private void executeTestSet( SurefireTestSet testSet, RunListener 
reporter, ClassLoader classLoader )

http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/78dca27e/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
----------------------------------------------------------------------
diff --git 
a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
 
b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
index e3b87af..3f0e57f 100644
--- 
a/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
+++ 
b/surefire-providers/surefire-junit4/src/main/java/org/apache/maven/surefire/junit4/JUnit4Provider.java
@@ -55,6 +55,8 @@ import org.junit.runner.Runner;
 import org.junit.runner.manipulation.Filter;
 import org.junit.runner.notification.RunNotifier;
 
+import static 
org.apache.maven.surefire.testset.TestListResolver.toClassFileName;
+
 /**
  * @author Kristian Rosenvold
  */
@@ -267,8 +269,6 @@ public class JUnit4Provider
     private static void execute( Class<?> testClass, RunNotifier notifier, 
Filter filter )
     {
         final int classModifiers = testClass.getModifiers();
-
-        // filter.shouldRunClass( testClass )
         if ( !Modifier.isAbstract( classModifiers ) && !Modifier.isInterface( 
classModifiers ) )
         {
             Runner runner = Request.aClass( testClass ).filterWith( filter 
).getRunner();
@@ -349,8 +349,6 @@ public class JUnit4Provider
     {
         private final TestListResolver methodFilter = 
JUnit4Provider.this.testResolver.createMethodFilters();
 
-        private final TestsToRun testsToRun = JUnit4Provider.this.testsToRun;
-
         @Override
         public boolean shouldRun( Description description )
         {
@@ -360,7 +358,7 @@ public class JUnit4Provider
             final boolean isValidTest = description.isTest() && cm.isValid();
             final String clazz = cm.getClazz();
             final String method = cm.getMethod();
-            return isSuite || isValidTest && methodFilter.shouldRun( 
testsToRun.getClassByName( clazz ), method );
+            return isSuite || isValidTest && methodFilter.shouldRun( 
toClassFileName( clazz ), method );
         }
 
         @Override

Reply via email to