[SUREFIRE-580] Allow "fail fast" or stop running on first failure
Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/2a944f06 Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/2a944f06 Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/2a944f06 Branch: refs/heads/master Commit: 2a944f0687ee849003403e401d69750af856fc47 Parents: 8c34806 Author: Tibor17 <tibo...@lycos.com> Authored: Thu Sep 3 01:33:26 2015 +0200 Committer: Tibor17 <tibo...@lycos.com> Committed: Sun Sep 6 22:57:59 2015 +0200 ---------------------------------------------------------------------- .../plugin/failsafe/IntegrationTestMojo.java | 16 + .../plugin/surefire/AbstractSurefireMojo.java | 48 +- .../surefire/SurefireExecutionParameters.java | 2 + .../plugin/surefire/SurefireProperties.java | 57 +-- .../surefire/booterclient/BooterSerializer.java | 1 + .../surefire/booterclient/ForkStarter.java | 219 +++++---- .../surefire/booterclient/MockReporter.java | 160 ------- .../lazytestprovider/AbstractCommandStream.java | 122 +++++ .../AbstractForkInputStream.java | 61 +++ .../lazytestprovider/NotifiableTestStream.java | 14 + .../lazytestprovider/TestLessInputStream.java | 453 +++++++++++++++++++ .../TestProvidingInputStream.java | 169 +++---- .../booterclient/output/ForkClient.java | 7 + .../surefire/report/TestSetRunListener.java | 4 + ...erDeserializerProviderConfigurationTest.java | 2 +- ...terDeserializerStartupConfigurationTest.java | 2 +- .../surefire/booterclient/MockReporter.java | 161 +++++++ .../TestLessInputStreamBuilderTest.java | 128 ++++++ .../TestProvidingInputStreamTest.java | 15 +- .../apache/maven/surefire/JUnit4SuiteTest.java | 2 + .../maven/plugin/surefire/SurefirePlugin.java | 17 +- .../site/apt/examples/skip-after-failure.apt.vm | 58 +++ maven-surefire-plugin/src/site/site.xml | 1 + .../surefire/booter/BaseProviderFactory.java | 20 +- .../apache/maven/surefire/booter/Command.java | 38 +- .../maven/surefire/booter/FailFastAware.java | 31 ++ .../surefire/booter/ForkingReporterFactory.java | 2 +- .../surefire/booter/ForkingRunListener.java | 17 +- .../surefire/booter/MasterProcessCommand.java | 12 +- .../surefire/booter/MasterProcessListener.java | 28 ++ .../surefire/booter/MasterProcessReader.java | 421 +++++++++++++++++ .../surefire/booter/SurefireReflector.java | 5 + .../surefire/booter/TwoPropertiesWrapper.java | 50 ++ .../providerapi/ProviderParameters.java | 11 + .../maven/surefire/report/RunListener.java | 8 +- .../surefire/report/SimpleReportEntry.java | 5 + .../booter/MasterProcessCommandTest.java | 3 +- .../maven/surefire/report/MockReporter.java | 9 + .../maven/surefire/booter/BooterConstants.java | 1 + .../surefire/booter/BooterDeserializer.java | 8 +- .../maven/surefire/booter/ForkedBooter.java | 8 +- .../surefire/booter/MasterProcessListener.java | 28 -- .../surefire/booter/MasterProcessReader.java | 257 ----------- .../surefire/booter/ProviderConfiguration.java | 15 +- .../maven/surefire/booter/ProviderFactory.java | 13 +- .../booter/MasterProcessReaderTest.java | 67 ++- .../surefire/common/junit4/JUnit4Reflector.java | 103 ++++- .../common/junit4/JUnit4RunListener.java | 64 +-- .../maven/surefire/common/junit4/Notifier.java | 106 +++++ .../maven/surefire/junit4/MockReporter.java | 11 +- .../common/junit4/JUnit4Reflector40Test.java | 28 ++ .../maven/surefire/junit/JUnitTestSetTest.java | 9 + .../surefire/junit4/JUnit4FailFastListener.java | 48 ++ .../maven/surefire/junit4/JUnit4Provider.java | 193 +++++--- .../junitcore/ConcurrentRunListener.java | 19 +- .../surefire/junitcore/FilteringRequest.java | 54 +++ .../junitcore/JUnit47FailFastListener.java | 65 +++ .../maven/surefire/junitcore/JUnitCore.java | 90 ++++ .../surefire/junitcore/JUnitCoreProvider.java | 125 +++-- .../surefire/junitcore/JUnitCoreWrapper.java | 159 +++++-- .../maven/surefire/junitcore/Stoppable.java | 32 ++ .../junitcore/JUnit4Reflector481Test.java | 52 +++ .../surefire/junitcore/Surefire746Test.java | 4 +- .../surefire-testng-utils/pom.xml | 2 +- .../testng/utils/FailFastEventsSingleton.java | 53 +++ .../surefire/testng/utils/FailFastListener.java | 77 ++++ .../surefire/testng/utils/FailFastNotifier.java | 50 ++ .../maven/surefire/testng/utils/Stoppable.java | 32 ++ .../testng/TestNGDirectoryTestSuite.java | 13 +- .../maven/surefire/testng/TestNGExecutor.java | 34 +- .../maven/surefire/testng/TestNGProvider.java | 114 +++-- .../surefire/testng/TestNGXmlTestSuite.java | 12 +- 72 files changed, 3337 insertions(+), 988 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java ---------------------------------------------------------------------- diff --git a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java index 2c2339a..eb315ce 100644 --- a/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java +++ b/maven-failsafe-plugin/src/main/java/org/apache/maven/plugin/failsafe/IntegrationTestMojo.java @@ -301,6 +301,17 @@ public class IntegrationTestMojo @Parameter( property = "failsafe.excludesFile" ) private File excludesFile; + /** + * Set to "true" to skip remaining tests after first test failure appeared. + * Due to race conditions in parallel/forked execution this may not be fully guaranteed.<br/> + * Enable with system property -Dfailsafe.skipAfterFailureCount=1 or any number greater than zero. + * Defaults to "0". + * + * @since 2.19 + */ + @Parameter( property = "failsafe.skipAfterFailureCount", defaultValue = "0" ) + private int skipAfterFailureCount; + protected int getRerunFailingTestsCount() { return rerunFailingTestsCount; @@ -594,6 +605,11 @@ public class IntegrationTestMojo this.failIfNoSpecifiedTests = failIfNoSpecifiedTests; } + public int getSkipAfterFailureCount() + { + return skipAfterFailureCount; + } + @Override public List<String> getIncludes() { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java index 4af79f2..f847604 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java @@ -683,6 +683,29 @@ public abstract class AbstractSurefireMojo protected abstract int getRerunFailingTestsCount(); + public abstract List<String> getIncludes(); + + public abstract File getIncludesFile(); + + public abstract void setIncludes( List<String> includes ); + + public abstract File getExcludesFile(); + + public abstract File[] getSuiteXmlFiles(); + + public abstract void setSuiteXmlFiles( File[] suiteXmlFiles ); + + public abstract String getRunOrder(); + + public abstract void setRunOrder( String runOrder ); + + protected abstract void handleSummary( RunResult summary, Exception firstForkException ) + throws MojoExecutionException, MojoFailureException; + + protected abstract String[] getDefaultIncludes(); + + protected abstract boolean isSkipExecution(); + private SurefireDependencyResolver dependencyResolver; private TestListResolver specificTests; @@ -807,8 +830,6 @@ public abstract class AbstractSurefireMojo return true; } - protected abstract boolean isSkipExecution(); - private void executeAfterPreconditionsChecked( DefaultScanResult scanResult ) throws MojoExecutionException, MojoFailureException { @@ -1031,9 +1052,6 @@ public abstract class AbstractSurefireMojo } } - protected abstract void handleSummary( RunResult summary, Exception firstForkException ) - throws MojoExecutionException, MojoFailureException; - protected void logReportsDirectory() { logDebugOrCliShowErrors( @@ -1448,7 +1466,7 @@ public abstract class AbstractSurefireMojo reporterConfiguration, testNg, // Not really used in provider. Limited to de/serializer. testSuiteDefinition, providerProperties, null, - false, cli ); + false, cli, getSkipAfterFailureCount() ); } private static Map<String, String> toStringProperties( Properties properties ) @@ -2032,8 +2050,6 @@ public abstract class AbstractSurefireMojo : new ClassLoaderConfiguration( false, false ); } - protected abstract String[] getDefaultIncludes(); - /** * Generate the test classpath. * @@ -2591,19 +2607,11 @@ public abstract class AbstractSurefireMojo } } - public abstract List<String> getIncludes(); - - public abstract File getIncludesFile(); - - public abstract void setIncludes( List<String> includes ); - public List<String> getExcludes() { return excludes; } - public abstract File getExcludesFile(); - public void setExcludes( List<String> excludes ) { this.excludes = excludes; @@ -2805,10 +2813,6 @@ public abstract class AbstractSurefireMojo this.excludedGroups = excludedGroups; } - public abstract File[] getSuiteXmlFiles(); - - public abstract void setSuiteXmlFiles( File[] suiteXmlFiles ); - public String getJunitArtifactName() { return junitArtifactName; @@ -3046,10 +3050,6 @@ public abstract class AbstractSurefireMojo return parallelMavenExecution != null && parallelMavenExecution; } - public abstract String getRunOrder(); - - public abstract void setRunOrder( String runOrder ); - public String[] getDependenciesToScan() { return dependenciesToScan; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java index 65c4062..6bd09ff 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireExecutionParameters.java @@ -118,4 +118,6 @@ public interface SurefireExecutionParameters Boolean getFailIfNoSpecifiedTests(); void setFailIfNoSpecifiedTests( boolean failIfNoSpecifiedTests ); + + int getSkipAfterFailureCount(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java index 472c51a..d41d4d7 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java @@ -127,7 +127,6 @@ public class SurefireProperties return result; } - public void copyToSystemProperties() { @@ -197,34 +196,33 @@ public class SurefireProperties public void addList( List<?> items, String propertyPrefix ) { - if ( items == null || items.isEmpty() ) - { - return; - } - int i = 0; - for ( Object item : items ) + if ( items != null && !items.isEmpty() ) { - if ( item == null ) - { - throw new NullPointerException( propertyPrefix + i + " has null value" ); - } - - String[] stringArray = StringUtils.split( item.toString(), "," ); - - for ( String aStringArray : stringArray ) + int i = 0; + for ( Object item : items ) { - setProperty( propertyPrefix + i, aStringArray ); - i++; + if ( item == null ) + { + throw new NullPointerException( propertyPrefix + i + " has null value" ); + } + + String[] stringArray = StringUtils.split( item.toString(), "," ); + + for ( String aStringArray : stringArray ) + { + setProperty( propertyPrefix + i, aStringArray ); + i++; + } } } } public void setClasspath( String prefix, Classpath classpath ) { - List classpathElements = classpath.getClassPath(); + List<String> classpathElements = classpath.getClassPath(); for ( int i = 0; i < classpathElements.size(); ++i ) { - String element = (String) classpathElements.get( i ); + String element = classpathElements.get( i ); setProperty( prefix + i, element ); } } @@ -232,38 +230,26 @@ public class SurefireProperties private static SurefireProperties loadProperties( InputStream inStream ) throws IOException { - Properties p = new Properties(); - try { + Properties p = new Properties(); p.load( inStream ); + return new SurefireProperties( p ); } finally { close( inStream ); } - - return new SurefireProperties( p ); } public static SurefireProperties loadProperties( File file ) throws IOException { - if ( file != null ) - { - return loadProperties( new FileInputStream( file ) ); - } - - return new SurefireProperties(); + return file == null ? new SurefireProperties() : loadProperties( new FileInputStream( file ) ); } private static void close( InputStream inputStream ) { - if ( inputStream == null ) - { - return; - } - try { inputStream.close(); @@ -280,8 +266,5 @@ public class SurefireProperties { super.setProperty( key, value ); } - } - - } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java index 0164ba9..139e14b 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/BooterSerializer.java @@ -133,6 +133,7 @@ class BooterSerializer properties.setProperty( USEMANIFESTONLYJAR, String.valueOf( classLoaderConfig.isUseManifestOnlyJar() ) ); properties.setProperty( FAILIFNOTESTS, String.valueOf( booterConfiguration.isFailIfNoTests() ) ); properties.setProperty( PROVIDER_CONFIGURATION, providerConfiguration.getProviderClassName() ); + properties.setProperty( FAIL_FAST_COUNT, String.valueOf( booterConfiguration.getSkipAfterFailureCount() ) ); List<CommandLineOption> mainCliOptions = booterConfiguration.getMainCliOptions(); if ( mainCliOptions != null ) { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/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 4b2e184..632834f 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 @@ -20,22 +20,21 @@ package org.apache.maven.plugin.surefire.booterclient; */ import org.apache.maven.plugin.logging.Log; -import org.apache.maven.plugin.surefire.AbstractSurefireMojo; import org.apache.maven.plugin.surefire.CommonReflector; import org.apache.maven.plugin.surefire.StartupReportConfiguration; import org.apache.maven.plugin.surefire.SurefireProperties; +import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.AbstractForkInputStream; +import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.NotifiableTestStream; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.OutputStreamFlushableCommandline; +import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStream; import org.apache.maven.plugin.surefire.booterclient.output.ForkClient; import org.apache.maven.plugin.surefire.booterclient.output.ThreadedStreamConsumer; import org.apache.maven.plugin.surefire.report.DefaultReporterFactory; import org.apache.maven.shared.utils.cli.CommandLineException; import org.apache.maven.shared.utils.cli.CommandLineTimeOutException; -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; @@ -43,14 +42,11 @@ import org.apache.maven.surefire.booter.ProviderFactory; import org.apache.maven.surefire.booter.StartupConfiguration; import org.apache.maven.surefire.booter.SurefireBooterForkException; import org.apache.maven.surefire.booter.SurefireExecutionException; -import org.apache.maven.surefire.booter.SystemPropertyManager; import org.apache.maven.surefire.providerapi.SurefireProvider; import org.apache.maven.surefire.report.StackTraceWriter; import org.apache.maven.surefire.suite.RunResult; import org.apache.maven.surefire.testset.TestRequest; import org.apache.maven.surefire.util.DefaultScanResult; -import org.apache.maven.surefire.util.internal.DaemonThreadFactory; -import org.apache.maven.surefire.util.internal.StringUtils; import java.io.File; import java.io.IOException; @@ -59,6 +55,7 @@ import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Map; +import java.util.Properties; import java.util.Queue; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; @@ -70,10 +67,23 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLine; +import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.addShutDownHook; +import static org.apache.maven.shared.utils.cli.ShutdownHookUtils.removeShutdownHook; +import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME; +import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread; +import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory; +import static org.apache.maven.plugin.surefire.AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder; +import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider. + TestLessInputStream.TestLessInputStreamBuilder; import static org.apache.maven.surefire.booter.Classpath.join; -import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS; +import static org.apache.maven.surefire.booter.SystemPropertyManager.writePropertiesFile; +import static org.apache.maven.surefire.suite.RunResult.timeout; +import static org.apache.maven.surefire.suite.RunResult.failure; +import static org.apache.maven.surefire.suite.RunResult.SUCCESS; import static java.lang.StrictMath.min; /** @@ -92,7 +102,7 @@ import static java.lang.StrictMath.min; */ public class ForkStarter { - private final ThreadFactory threadFactory = DaemonThreadFactory.newDaemonThreadFactory(); + private final ThreadFactory threadFactory = newDaemonThreadFactory(); /** * Closes an InputStream @@ -180,9 +190,19 @@ public class ForkStarter { DefaultReporterFactory forkedReporterFactory = new DefaultReporterFactory( startupReportConfiguration ); defaultReporterFactories.add( forkedReporterFactory ); - final ForkClient forkClient = - new ForkClient( forkedReporterFactory, startupReportConfiguration.getTestVmSystemProperties() ); - return fork( null, new PropertiesWrapper( providerProperties ), forkClient, effectiveSystemProperties, null ); + PropertiesWrapper props = new PropertiesWrapper( providerProperties ); + ForkClient forkClient = + new ForkClient( forkedReporterFactory, startupReportConfiguration.getTestVmSystemProperties() ); + TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); + TestLessInputStream stream = builder.build(); + try + { + return fork( null, props, forkClient, effectiveSystemProperties, stream, false ); + } + finally + { + builder.removeStream( stream ); + } } private RunResult run( SurefireProperties effectiveSystemProperties ) @@ -209,41 +229,57 @@ public class ForkStarter private RunResult runSuitesForkOnceMultiple( final SurefireProperties effectiveSystemProperties, int forkCount ) throws SurefireBooterForkException { - ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount ); ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>( forkCount ) ); executorService.setThreadFactory( threadFactory ); try { - final Queue<Command> commands = new ConcurrentLinkedQueue<Command>(); + final Queue<String> tests = new ConcurrentLinkedQueue<String>(); + for ( Class<?> clazz : getSuitesIterator() ) { - commands.add( new Command( RUN_CLASS, clazz.getName() ) ); + tests.add( clazz.getName() ); + } + + final Queue<TestProvidingInputStream> testStreams = new ConcurrentLinkedQueue<TestProvidingInputStream>(); + + for ( int forkNum = 0, total = min( forkCount, tests.size() ); forkNum < total; forkNum++ ) + { + testStreams.add( new TestProvidingInputStream( tests ) ); } - Collection<TestProvidingInputStream> testStreams = new ArrayList<TestProvidingInputStream>(); - for ( int forkNum = 0, total = min( forkCount, commands.size() ); forkNum < total; forkNum++ ) + + final AtomicBoolean notifyStreamsToSkipTestsJustNow = new AtomicBoolean(); + Collection<Future<RunResult>> results = new ArrayList<Future<RunResult>>( forkCount ); + for ( final TestProvidingInputStream testProvidingInputStream : testStreams ) { - final TestProvidingInputStream testProvidingInputStream = new TestProvidingInputStream( commands ); - testStreams.add( testProvidingInputStream ); Callable<RunResult> pf = new Callable<RunResult>() { public RunResult call() throws Exception { - DefaultReporterFactory forkedReporterFactory = - new DefaultReporterFactory( startupReportConfiguration ); - defaultReporterFactories.add( forkedReporterFactory ); - ForkClient forkClient = new ForkClient( forkedReporterFactory, - startupReportConfiguration.getTestVmSystemProperties(), - testProvidingInputStream ); + DefaultReporterFactory reporter = new DefaultReporterFactory( startupReportConfiguration ); + defaultReporterFactories.add( reporter ); + + Properties vmProps = startupReportConfiguration.getTestVmSystemProperties(); + + ForkClient forkClient = new ForkClient( reporter, vmProps, testProvidingInputStream ) + { + @Override + protected void stopOnNextTest() + { + if ( notifyStreamsToSkipTestsJustNow.compareAndSet( false, true ) ) + { + notifyStreamsToSkipTests( testStreams ); + } + } + }; return fork( null, new PropertiesWrapper( providerConfiguration.getProviderProperties() ), - forkClient, effectiveSystemProperties, testProvidingInputStream ); + forkClient, effectiveSystemProperties, testProvidingInputStream, true ); } }; results.add( executorService.submit( pf ) ); } - dispatchTestSetFinished( testStreams ); return awaitResultsDone( results, executorService ); } finally @@ -252,6 +288,14 @@ public class ForkStarter } } + private static void notifyStreamsToSkipTests( Collection<? extends NotifiableTestStream> notifiableTestStreams ) + { + for ( NotifiableTestStream notifiableTestStream : notifiableTestStreams ) + { + notifiableTestStream.skipSinceNextTest(); + } + } + @SuppressWarnings( "checkstyle:magicnumber" ) private RunResult runSuitesForkPerTestSet( final SurefireProperties effectiveSystemProperties, int forkCount ) throws SurefireBooterForkException @@ -262,6 +306,8 @@ public class ForkStarter executorService.setThreadFactory( threadFactory ); try { + final AtomicBoolean notifyStreamsToSkipTestsJustNow = new AtomicBoolean(); + final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); for ( final Object testSet : getSuitesIterator() ) { Callable<RunResult> pf = new Callable<RunResult>() @@ -272,10 +318,29 @@ public class ForkStarter DefaultReporterFactory forkedReporterFactory = new DefaultReporterFactory( startupReportConfiguration ); defaultReporterFactories.add( forkedReporterFactory ); - ForkClient forkClient = new ForkClient( forkedReporterFactory, - startupReportConfiguration.getTestVmSystemProperties() ); - return fork( testSet, new PropertiesWrapper( providerConfiguration.getProviderProperties() ), - forkClient, effectiveSystemProperties, null ); + Properties vmProps = startupReportConfiguration.getTestVmSystemProperties(); + ForkClient forkClient = new ForkClient( forkedReporterFactory, vmProps ) + { + @Override + protected void stopOnNextTest() + { + if ( notifyStreamsToSkipTestsJustNow.compareAndSet( false, true ) ) + { + builder.getCachableCommands().skipSinceNextTest(); + } + } + }; + TestLessInputStream stream = builder.build(); + try + { + return fork( testSet, + new PropertiesWrapper( providerConfiguration.getProviderProperties() ), + forkClient, effectiveSystemProperties, stream, false ); + } + finally + { + builder.removeStream( stream ); + } } }; results.add( executorService.submit( pf ) ); @@ -314,20 +379,14 @@ public class ForkStarter } catch ( ExecutionException e ) { - throw new SurefireBooterForkException( "ExecutionException", e ); + Throwable realException = e.getCause(); + String error = realException == null ? "" : realException.getLocalizedMessage(); + throw new SurefireBooterForkException( "ExecutionException " + error, realException ); } } return globalResult; } - private static void dispatchTestSetFinished( Iterable<TestProvidingInputStream> testStreams ) - { - for ( TestProvidingInputStream testStream : testStreams ) - { - testStream.testSetFinished(); - } - } - @SuppressWarnings( "checkstyle:magicnumber" ) private void closeExecutor( ExecutorService executorService ) throws SurefireBooterForkException @@ -347,14 +406,14 @@ public class ForkStarter private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient, SurefireProperties effectiveSystemProperties, - TestProvidingInputStream testProvidingInputStream ) + AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream ) throws SurefireBooterForkException { int forkNumber = ForkNumberBucket.drawNumber(); try { return fork( testSet, providerProperties, forkClient, effectiveSystemProperties, forkNumber, - testProvidingInputStream ); + testProvidingInputStream, readTestsFromInStream ); } finally { @@ -364,28 +423,30 @@ public class ForkStarter private RunResult fork( Object testSet, KeyValueSource providerProperties, ForkClient forkClient, SurefireProperties effectiveSystemProperties, int forkNumber, - TestProvidingInputStream testProvidingInputStream ) + AbstractForkInputStream testProvidingInputStream, boolean readTestsFromInStream ) throws SurefireBooterForkException { - File surefireProperties; - File systPropsFile = null; + final File surefireProperties; + final File systPropsFile; try { BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration ); - surefireProperties = - booterSerializer.serialize( providerProperties, providerConfiguration, startupConfiguration, testSet, - null != testProvidingInputStream ); + surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration, + startupConfiguration, testSet, readTestsFromInStream ); if ( effectiveSystemProperties != null ) { SurefireProperties filteredProperties = - AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, - forkNumber ); - systPropsFile = - SystemPropertyManager.writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(), - "surefire_" + systemPropertiesFileCounter++, - forkConfiguration.isDebug() ); + createCopyAndReplaceForkNumPlaceholder( effectiveSystemProperties, forkNumber ); + + systPropsFile = writePropertiesFile( filteredProperties, forkConfiguration.getTempDirectory(), + "surefire_" + systemPropertiesFileCounter++, + forkConfiguration.isDebug() ); + } + else + { + systPropsFile = null; } } catch ( IOException e ) @@ -407,23 +468,14 @@ public class ForkStarter log.debug( bootClasspath.getLogMessage( "boot" ) ); log.debug( bootClasspath.getCompactLogMessage( "boot(compact)" ) ); } + OutputStreamFlushableCommandline cli = forkConfiguration.createCommandLine( bootClasspath.getClassPath(), startupConfiguration, forkNumber ); - final InputStreamCloser inputStreamCloser; - final Thread inputStreamCloserHook; - if ( testProvidingInputStream != null ) - { - testProvidingInputStream.setFlushReceiverProvider( cli ); - inputStreamCloser = new InputStreamCloser( testProvidingInputStream ); - inputStreamCloserHook = DaemonThreadFactory.newDaemonThread( inputStreamCloser, "input-stream-closer" ); - ShutdownHookUtils.addShutDownHook( inputStreamCloserHook ); - } - else - { - inputStreamCloser = null; - inputStreamCloserHook = null; - } + InputStreamCloser inputStreamCloser = new InputStreamCloser( testProvidingInputStream ); + Thread inputStreamCloserHook = newDaemonThread( inputStreamCloser, "input-stream-closer" ); + testProvidingInputStream.setFlushReceiverProvider( cli ); + addShutDownHook( inputStreamCloserHook ); cli.createArg().setFile( surefireProperties ); @@ -444,11 +496,12 @@ public class ForkStarter try { final int timeout = forkedProcessTimeoutInSeconds > 0 ? forkedProcessTimeoutInSeconds : 0; - final int result = - CommandLineUtils.executeCommandLine( cli, testProvidingInputStream, threadedStreamConsumer, - threadedStreamConsumer, timeout, inputStreamCloser, - Charset.forName( StringUtils.FORK_STREAM_CHARSET_NAME ) ); - if ( result != RunResult.SUCCESS ) + + final int result = executeCommandLine( cli, testProvidingInputStream, threadedStreamConsumer, + threadedStreamConsumer, timeout, inputStreamCloser, + Charset.forName( FORK_STREAM_CHARSET_NAME ) ); + + if ( result != SUCCESS ) { throw new SurefireBooterForkException( "Error occurred in starting fork, check output in log" ); } @@ -456,27 +509,24 @@ public class ForkStarter } catch ( CommandLineTimeOutException e ) { - runResult = RunResult.timeout( - forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() ); + runResult = timeout( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult() ); } catch ( CommandLineException e ) { - runResult = - RunResult.failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e ); + runResult = failure( forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(), e ); throw new SurefireBooterForkException( "Error while executing forked tests.", e.getCause() ); } finally { threadedStreamConsumer.close(); - if ( inputStreamCloser != null ) - { - inputStreamCloser.run(); - ShutdownHookUtils.removeShutdownHook( inputStreamCloserHook ); - } + inputStreamCloser.run(); + removeShutdownHook( inputStreamCloserHook ); + if ( runResult == null ) { runResult = forkClient.getDefaultReporterFactory().getGlobalRunStatistics().getRunResult(); } + if ( !runResult.isTimeout() ) { StackTraceWriter errorInFork = forkClient.getErrorInFork(); @@ -512,9 +562,8 @@ public class ForkStarter CommonReflector commonReflector = new CommonReflector( unifiedClassLoader ); Object reporterFactory = commonReflector.createReportingReporterFactory( startupReportConfiguration ); - final ProviderFactory providerFactory = - new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader, - reporterFactory ); + ProviderFactory providerFactory = + new ProviderFactory( startupConfiguration, providerConfiguration, unifiedClassLoader, reporterFactory ); SurefireProvider surefireProvider = providerFactory.createProvider( false ); return surefireProvider.getSuites(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java deleted file mode 100644 index b80f80f..0000000 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.apache.maven.plugin.surefire.booterclient; - -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; -import org.apache.maven.surefire.report.ConsoleLogger; -import org.apache.maven.surefire.report.ConsoleOutputReceiver; -import org.apache.maven.surefire.report.ReportEntry; -import org.apache.maven.surefire.report.RunListener; - -/** - * Internal use only - */ -public class MockReporter - implements RunListener, ConsoleLogger, ConsoleOutputReceiver -{ - private final List<String> events = new ArrayList<String>(); - - private final List<Object> data = new ArrayList<Object>(); - - public static final String SET_STARTING = "SET_STARTED"; - - public static final String SET_COMPLETED = "SET_COMPLETED"; - - public static final String TEST_STARTING = "TEST_STARTED"; - - public static final String TEST_SUCCEEDED = "TEST_COMPLETED"; - - public static final String TEST_FAILED = "TEST_FAILED"; - - public static final String TEST_ERROR = "TEST_ERROR"; - - public static final String TEST_SKIPPED = "TEST_SKIPPED"; - - public static final String TEST_ASSUMPTION_FAIL = "TEST_ASSUMPTION_SKIPPED"; - - public static final String CONSOLE_OUTPUT = "CONSOLE_OUTPUT"; - - public static final String STDOUT = "STDOUT"; - - public static final String STDERR = "STDERR"; - - private final AtomicInteger testSucceeded = new AtomicInteger(); - - private final AtomicInteger testIgnored = new AtomicInteger(); - - private final AtomicInteger testFailed = new AtomicInteger(); - - public MockReporter() - { - } - - public void testSetStarting( ReportEntry report ) - { - events.add( SET_STARTING ); - data.add( report ); - } - - public void testSetCompleted( ReportEntry report ) - { - events.add( SET_COMPLETED ); - data.add( report ); - } - - public void testStarting( ReportEntry report ) - { - events.add( TEST_STARTING ); - data.add( report ); - } - - public void testSucceeded( ReportEntry report ) - { - events.add( TEST_SUCCEEDED ); - testSucceeded.incrementAndGet(); - data.add( report ); - } - - public void testError( ReportEntry report ) - { - events.add( TEST_ERROR ); - data.add( report ); - testFailed.incrementAndGet(); - } - - public void testFailed( ReportEntry report ) - { - events.add( TEST_FAILED ); - data.add( report ); - testFailed.incrementAndGet(); - } - - - public void testSkipped( ReportEntry report ) - { - events.add( TEST_SKIPPED ); - data.add( report ); - testIgnored.incrementAndGet(); - } - - - public List<String> getEvents() - { - return events; - } - - public List getData() - { - return data; - } - - public String getFirstEvent() - { - return events.get( 0 ); - } - - public ReportEntry getFirstData() - { - return (ReportEntry) data.get( 0 ); - } - - - public void testAssumptionFailure( ReportEntry report ) - { - events.add( TEST_ASSUMPTION_FAIL ); - data.add( report ); - testIgnored.incrementAndGet(); - - } - - public void info( String message ) - { - events.add( CONSOLE_OUTPUT ); - data.add( message ); - } - - public void writeTestOutput( byte[] buf, int off, int len, boolean stdout ) - { - events.add( stdout ? STDOUT : STDERR ); - data.add( new String( buf, off, len ) ); - } -} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java new file mode 100644 index 0000000..4d6331c --- /dev/null +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractCommandStream.java @@ -0,0 +1,122 @@ +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 java.io.IOException; + +/** + * Reader stream sends commands to forked jvm std-{@link java.io.InputStream input-stream}. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + * @see org.apache.maven.surefire.booter.Command + */ +public abstract class AbstractCommandStream + extends AbstractForkInputStream +{ + private byte[] currentBuffer; + private int currentPos; + private volatile MasterProcessCommand lastCommand; + + protected abstract boolean isClosed(); + + /** + * Unnecessarily opposite to {@link #isClosed()} however may respect + * {@link #getLastCommand() last command} and {@link #isClosed()}. + */ + protected abstract boolean canContinue(); + + /** + * Possibly waiting for next command (see {@link #nextCommand()}) unless the stream is atomically + * closed (see {@link #isClosed()} returns {@code true}) before this method has returned. + */ + protected void beforeNextCommand() + throws IOException + { + } + + protected abstract Command nextCommand(); + + /** + * Returns quietly and immediately. + */ + protected final void invalidateInternalBuffer() + { + currentBuffer = null; + currentPos = 0; + } + + protected final MasterProcessCommand getLastCommand() + { + return lastCommand; + } + + /** + * Used by single thread in StreamFeeder class. + * + * @return {@inheritDoc} + * @throws IOException {@inheritDoc} + */ + @SuppressWarnings( "checkstyle:magicnumber" ) + @Override + public int read() + throws IOException + { + if ( isClosed() ) + { + return -1; + } + + byte[] buffer = currentBuffer; + if ( buffer == null ) + { + tryFlush(); + + if ( !canContinue() ) + { + close(); + return -1; + } + + beforeNextCommand(); + + if ( isClosed() ) + { + return -1; + } + + Command cmd = nextCommand(); + lastCommand = cmd.getCommandType(); + buffer = lastCommand.hasDataType() ? lastCommand.encode( cmd.getData() ) : lastCommand.encode(); + } + + int b = buffer[currentPos++] & 0xff; + if ( currentPos == buffer.length ) + { + buffer = null; + currentPos = 0; + } + currentBuffer = buffer; + return b; + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java new file mode 100644 index 0000000..281c05d --- /dev/null +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/AbstractForkInputStream.java @@ -0,0 +1,61 @@ +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 java.io.IOException; +import java.io.InputStream; + +import static org.apache.maven.surefire.util.internal.StringUtils.requireNonNull; + +/** + * Reader stream sends bytes to forked jvm std-{@link InputStream input-stream}. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +public abstract class AbstractForkInputStream + extends InputStream + implements NotifiableTestStream +{ + private volatile FlushReceiverProvider flushReceiverProvider; + + /** + * @param flushReceiverProvider the provider for a flush receiver. + */ + public void setFlushReceiverProvider( FlushReceiverProvider flushReceiverProvider ) + { + this.flushReceiverProvider = requireNonNull( flushReceiverProvider ); + } + + protected boolean tryFlush() + throws IOException + { + if ( flushReceiverProvider != null ) + { + FlushReceiver flushReceiver = flushReceiverProvider.getFlushReceiver(); + if ( flushReceiver != null ) + { + flushReceiver.flush(); + return true; + } + } + return false; + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java index 47aa642..d47645b 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/NotifiableTestStream.java @@ -28,5 +28,19 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider; */ public interface NotifiableTestStream { + /** + * Notifies {@link TestProvidingInputStream} in order to dispatch a new test back to the forked + * jvm (particular fork which hits this call); or do nothing in {@link TestLessInputStream}. + */ void provideNewTest(); + + /** + * Sends an event to a fork jvm in order to skip tests. + * Returns immediately without blocking. + */ + void skipSinceNextTest(); + + void shutdown(); + + void noop(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java new file mode 100644 index 0000000..b608620 --- /dev/null +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStream.java @@ -0,0 +1,453 @@ +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 java.io.IOException; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static org.apache.maven.surefire.booter.Command.NOOP; +import static org.apache.maven.surefire.booter.Command.SHUTDOWN; +import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST; + +/** + * Dispatches commands without tests. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +public final class TestLessInputStream + extends AbstractCommandStream +{ + private final Semaphore barrier = new Semaphore( 0 ); + + private final AtomicBoolean closed = new AtomicBoolean(); + + private final Queue<Command> immediateCommands = new ConcurrentLinkedQueue<Command>(); + + private final TestLessInputStreamBuilder builder; + + private Iterator<Command> cachableCommands; + + private TestLessInputStream( TestLessInputStreamBuilder builder ) + { + this.builder = builder; + } + + public void provideNewTest() + { + } + + public void skipSinceNextTest() + { + if ( canContinue() ) + { + immediateCommands.add( SKIP_SINCE_NEXT_TEST ); + barrier.release(); + } + } + + public void shutdown() + { + if ( canContinue() ) + { + immediateCommands.add( SHUTDOWN ); + barrier.release(); + } + } + + public void noop() + { + if ( canContinue() ) + { + immediateCommands.add( NOOP ); + barrier.release(); + } + } + + @Override + protected boolean isClosed() + { + return closed.get(); + } + + @Override + protected boolean canContinue() + { + return !isClosed(); + } + + @Override + protected Command nextCommand() + { + Command cmd = immediateCommands.poll(); + if ( cmd == null ) + { + if ( cachableCommands == null ) + { + cachableCommands = builder.getIterableCachable().iterator(); + } + + cmd = cachableCommands.next(); + } + return cmd; + } + + @Override + protected void beforeNextCommand() + throws IOException + { + awaitNextCommand(); + } + + @Override + public void close() + { + if ( closed.compareAndSet( false, true ) ) + { + invalidateInternalBuffer(); + barrier.drainPermits(); + barrier.release(); + } + } + + /** + * For testing purposes only. + * + * @return permits used internally by {@link #beforeNextCommand()} + */ + int availablePermits() + { + return barrier.availablePermits(); + } + + private void awaitNextCommand() + throws IOException + { + try + { + barrier.acquire(); + } + catch ( InterruptedException e ) + { + // help GC to free this object because StreamFeeder Thread cannot read it anyway after IOE + invalidateInternalBuffer(); + throw new IOException( e.getLocalizedMessage() ); + } + } + + /** + * todo + */ + public static final class TestLessInputStreamBuilder + { + ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); + private final Queue<TestLessInputStream> aliveStreams = new ConcurrentLinkedQueue<TestLessInputStream>(); + private final ImmediateCommands immediateCommands = new ImmediateCommands(); + private final CachableCommands cachableCommands = new CachableCommands(); + private final Node head = new Node( null ); + private final Iterable<Command> iterableCachable; + + public TestLessInputStreamBuilder() + { + iterableCachable = new Iterable<Command>() + { + public Iterator<Command> iterator() + { + return new CIt(); + } + }; + } + + public TestLessInputStream build() + { + Lock lock = rwLock.writeLock(); + lock.lock(); + try + { + TestLessInputStream is = new TestLessInputStream( this ); + aliveStreams.offer( is ); + return is; + } + finally + { + lock.unlock(); + } + } + + public void removeStream( TestLessInputStream is ) + { + Lock lock = rwLock.writeLock(); + lock.lock(); + try + { + aliveStreams.remove( is ); + } + finally + { + lock.unlock(); + } + } + + public NotifiableTestStream getImmediateCommands() + { + return immediateCommands; + } + + public NotifiableTestStream getCachableCommands() + { + return cachableCommands; + } + + /** + * The iterator is not thread safe. + */ + Iterable<Command> getIterableCachable() + { + return iterableCachable; + } + + @SuppressWarnings( "checkstyle:innerassignment" ) + private void addTailNode( Command command ) + { + Node newTail = new Node( command ); + Node currentTail = head; + do + { + for ( Node successor; ( successor = currentTail.next.get() ) != null; ) + { + currentTail = successor; + } + } while ( !currentTail.next.compareAndSet( null, newTail ) ); + } + + @SuppressWarnings( "checkstyle:innerassignment" ) + private boolean addTailNodeIfAbsent( Command command ) + { + Node newTail = new Node( command ); + Node currentTail = head; + do + { + for ( Node successor; ( successor = currentTail.next.get() ) != null; ) + { + currentTail = successor; + if ( command.equals( currentTail.command ) ) + { + return false; + } + } + } while ( !currentTail.next.compareAndSet( null, newTail ) ); + return true; + } + + private static Node nextCachedNode( Node current ) + { + return current.next.get(); + } + + private final class CIt + implements Iterator<Command> + { + private Node node = TestLessInputStreamBuilder.this.head; + + public boolean hasNext() + { + return examineNext( false ) != null; + } + + public Command next() + { + Command command = examineNext( true ); + if ( command == null ) + { + throw new NoSuchElementException(); + } + return command; + } + + public void remove() + { + throw new UnsupportedOperationException(); + } + + private Command examineNext( boolean store ) + { + Node next = nextCachedNode( node ); + if ( store && next != null ) + { + node = next; + } + return next == null ? null : next.command; + } + } + + /** + * Event is called just now for all alive streams and command is not persisted. + */ + private final class ImmediateCommands + implements NotifiableTestStream + { + public void provideNewTest() + { + } + + public void skipSinceNextTest() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams ) + { + aliveStream.skipSinceNextTest(); + } + } + finally + { + lock.unlock(); + } + } + + public void shutdown() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams ) + { + aliveStream.shutdown(); + } + } + finally + { + lock.unlock(); + } + } + + public void noop() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams ) + { + aliveStream.noop(); + } + } + finally + { + lock.unlock(); + } + } + } + + /** + * Event is persisted. + */ + private final class CachableCommands + implements NotifiableTestStream + { + public void provideNewTest() + { + } + + public void skipSinceNextTest() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + if ( TestLessInputStreamBuilder.this.addTailNodeIfAbsent( SKIP_SINCE_NEXT_TEST ) ) + { + release(); + } + } + finally + { + lock.unlock(); + } + } + + public void shutdown() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + if ( TestLessInputStreamBuilder.this.addTailNodeIfAbsent( SHUTDOWN ) ) + { + release(); + } + } + finally + { + lock.unlock(); + } + } + + public void noop() + { + Lock lock = rwLock.readLock(); + lock.lock(); + try + { + if ( TestLessInputStreamBuilder.this.addTailNodeIfAbsent( NOOP ) ) + { + release(); + } + } + finally + { + lock.unlock(); + } + } + + private void release() + { + for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams ) + { + aliveStream.barrier.release(); + } + } + } + + private static class Node + { + private final AtomicReference<Node> next = new AtomicReference<Node>(); + private final Command command; + + Node( Command command ) + { + this.command = command; + } + } + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/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 97bf24d..dcc2997 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 @@ -20,144 +20,136 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider; */ 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.ConcurrentLinkedQueue; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicBoolean; +import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS; import static org.apache.maven.surefire.booter.MasterProcessCommand.TEST_SET_FINISHED; -import static org.apache.maven.surefire.util.internal.StringUtils.requireNonNull; +import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST; +import static org.apache.maven.surefire.booter.Command.SHUTDOWN; +import static org.apache.maven.surefire.booter.Command.NOOP; /** - * An {@link InputStream} that, when read, provides test class names out of a queue. + * An {@link java.io.InputStream} that, when read, provides test class names out of a queue. * <p/> * The Stream provides only one test at a time, but only after {@link #provideNewTest()} has been invoked. * <p/> * After providing each test class name, followed by a newline character, a flush is performed on the * {@link FlushReceiver} provided by the {@link FlushReceiverProvider} that can be set using * {@link #setFlushReceiverProvider(FlushReceiverProvider)}. + * <p/> + * The instance is used only in reusable forks in {@link org.apache.maven.plugin.surefire.booterclient.ForkStarter} + * by one Thread. * * @author Andreas Gudian + * @author Tibor Digana (tibor17) */ -public class TestProvidingInputStream - extends InputStream - implements NotifiableTestStream +public final class TestProvidingInputStream + extends AbstractCommandStream { - private final Semaphore semaphore = new Semaphore( 0 ); + private final Semaphore barrier = new Semaphore( 0 ); - private final Queue<Command> commands; + private final Queue<Command> commands = new ConcurrentLinkedQueue<Command>(); private final AtomicBoolean closed = new AtomicBoolean(); - private byte[] currentBuffer; - - private int currentPos; - - private MasterProcessCommand lastCommand; - - private volatile FlushReceiverProvider flushReceiverProvider; + private final Queue<String> testClassNames; /** * C'tor * - * @param commands source of the tests to be read from this stream + * @param testClassNames source of the tests to be read from this stream */ - public TestProvidingInputStream( Queue<Command> commands ) + public TestProvidingInputStream( Queue<String> testClassNames ) { - this.commands = commands; + this.testClassNames = testClassNames; } /** - * @param flushReceiverProvider the provider for a flush receiver. + * For testing purposes. */ - public void setFlushReceiverProvider( FlushReceiverProvider flushReceiverProvider ) + void testSetFinished() { - this.flushReceiverProvider = requireNonNull( flushReceiverProvider ); + if ( canContinue() ) + { + commands.add( Command.TEST_SET_FINISHED ); + barrier.release(); + } } - public void testSetFinished() + public void skipSinceNextTest() { - commands.add( new Command( TEST_SET_FINISHED ) ); + if ( canContinue() ) + { + commands.add( SKIP_SINCE_NEXT_TEST ); + barrier.release(); + } } - /** - * Used by single thread in StreamFeeder. - * - * @return {@inheritDoc} - * @throws IOException {@inheritDoc} - */ - @SuppressWarnings( "checkstyle:magicnumber" ) - @Override - public int read() - throws IOException + public void shutdown() { - byte[] buffer = currentBuffer; - if ( buffer == null ) + if ( canContinue() ) { - if ( flushReceiverProvider != null ) - { - FlushReceiver flushReceiver = flushReceiverProvider.getFlushReceiver(); - if ( flushReceiver != null ) - { - flushReceiver.flush(); - } - } - - if ( lastCommand == TEST_SET_FINISHED || closed.get() ) - { - close(); - return -1; - } - - awaitNextTest(); - - if ( closed.get() ) - { - return -1; - } - - Command command = commands.poll(); - lastCommand = command.getCommandType(); - String test = command.getData(); - buffer = lastCommand == TEST_SET_FINISHED ? lastCommand.encode() : lastCommand.encode( test ); + commands.add( SHUTDOWN ); + barrier.release(); } + } - int b = buffer[currentPos++] & 0xff; - if ( currentPos == buffer.length ) + public void noop() + { + if ( canContinue() ) { - buffer = null; - currentPos = 0; + commands.add( NOOP ); + barrier.release(); } - currentBuffer = buffer; - return b; } - private void awaitNextTest() - throws IOException + @Override + protected Command nextCommand() { - try + Command cmd = commands.poll(); + if ( cmd == null ) { - semaphore.acquire(); + String cmdData = testClassNames.poll(); + return cmdData == null ? Command.TEST_SET_FINISHED : new Command( RUN_CLASS, cmdData ); } - catch ( InterruptedException e ) + else { - // help GC to free this object because StreamFeeder Thread cannot read it after IOE - currentBuffer = null; - throw new IOException( e.getLocalizedMessage() ); + return cmd; } } + @Override + protected void beforeNextCommand() + throws IOException + { + awaitNextTest(); + } + + @Override + protected boolean isClosed() + { + return closed.get(); + } + + @Override + protected boolean canContinue() + { + return getLastCommand() != TEST_SET_FINISHED && !isClosed(); + } + /** * Signal that a new test is to be provided. */ public void provideNewTest() { - if ( !closed.get() ) + if ( canContinue() ) { - semaphore.release(); + barrier.release(); } } @@ -166,9 +158,24 @@ public class TestProvidingInputStream { if ( closed.compareAndSet( false, true ) ) { - currentBuffer = null; - semaphore.drainPermits(); - semaphore.release(); + invalidateInternalBuffer(); + barrier.drainPermits(); + barrier.release(); + } + } + + private void awaitNextTest() + throws IOException + { + try + { + barrier.acquire(); + } + catch ( InterruptedException e ) + { + // help GC to free this object because StreamFeeder Thread cannot read it anyway after IOE + invalidateInternalBuffer(); + throw new IOException( e.getLocalizedMessage() ); } } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java index 1778051..6cbc9bc 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/output/ForkClient.java @@ -75,6 +75,10 @@ public class ForkClient this.notifiableTestStream = notifiableTestStream; } + protected void stopOnNextTest() + { + } + public DefaultReporterFactory getDefaultReporterFactory() { return defaultReporterFactory; @@ -162,6 +166,9 @@ public class ForkClient case ForkingRunListener.BOOTERCODE_BYE: saidGoodBye = true; break; + case ForkingRunListener.BOOTERCODE_STOP_ON_NEXT_TEST: + stopOnNextTest(); + break; default: System.out.println( s ); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java index b7bcadb..497a316 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/TestSetRunListener.java @@ -217,6 +217,10 @@ public class TestSetRunListener clearCapture(); } + public void testExecutionSkippedByUser() + { + } + public void testAssumptionFailure( ReportEntry report ) { testSkipped( report ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java index 113e2de..9c8cc07 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerProviderConfigurationTest.java @@ -237,7 +237,7 @@ public class BooterDeserializerProviderConfigurationTest RunOrderParameters runOrderParameters = new RunOrderParameters( RunOrder.DEFAULT, null ); return new ProviderConfiguration( directoryScannerParameters, runOrderParameters, true, reporterConfiguration, new TestArtifactInfo( "5.0", "ABC" ), testSuiteDefinition, new HashMap<String, String>(), aTestTyped, - readTestsFromInStream, cli ); + readTestsFromInStream, cli, 0 ); } private StartupConfiguration getTestStartupConfiguration( ClassLoaderConfiguration classLoaderConfiguration ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java index 89c1f3e..6f232fa 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/BooterDeserializerStartupConfigurationTest.java @@ -144,7 +144,7 @@ public class BooterDeserializerStartupConfigurationTest RunOrderParameters runOrderParameters = new RunOrderParameters( RunOrder.DEFAULT, null ); return new ProviderConfiguration( directoryScannerParameters, runOrderParameters, true, reporterConfiguration, new TestArtifactInfo( "5.0", "ABC" ), testSuiteDefinition, new HashMap<String, String>(), - BooterDeserializerProviderConfigurationTest.aTestTyped, true, cli ); + BooterDeserializerProviderConfigurationTest.aTestTyped, true, cli, 0 ); } private StartupConfiguration getTestStartupConfiguration( ClassLoaderConfiguration classLoaderConfiguration ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/2a944f06/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java new file mode 100644 index 0000000..3f2d221 --- /dev/null +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/MockReporter.java @@ -0,0 +1,161 @@ +package org.apache.maven.plugin.surefire.booterclient; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import org.apache.maven.surefire.report.ConsoleLogger; +import org.apache.maven.surefire.report.ConsoleOutputReceiver; +import org.apache.maven.surefire.report.ReportEntry; +import org.apache.maven.surefire.report.RunListener; + +/** + * Internal tests use only. + */ +public class MockReporter + implements RunListener, ConsoleLogger, ConsoleOutputReceiver +{ + private final List<String> events = new ArrayList<String>(); + + private final List<Object> data = new ArrayList<Object>(); + + public static final String SET_STARTING = "SET_STARTED"; + + public static final String SET_COMPLETED = "SET_COMPLETED"; + + public static final String TEST_STARTING = "TEST_STARTED"; + + public static final String TEST_SUCCEEDED = "TEST_COMPLETED"; + + public static final String TEST_FAILED = "TEST_FAILED"; + + public static final String TEST_ERROR = "TEST_ERROR"; + + public static final String TEST_SKIPPED = "TEST_SKIPPED"; + + public static final String TEST_ASSUMPTION_FAIL = "TEST_ASSUMPTION_SKIPPED"; + + public static final String CONSOLE_OUTPUT = "CONSOLE_OUTPUT"; + + public static final String STDOUT = "STDOUT"; + + public static final String STDERR = "STDERR"; + + private final AtomicInteger testSucceeded = new AtomicInteger(); + + private final AtomicInteger testIgnored = new AtomicInteger(); + + private final AtomicInteger testFailed = new AtomicInteger(); + + public void testSetStarting( ReportEntry report ) + { + events.add( SET_STARTING ); + data.add( report ); + } + + public void testSetCompleted( ReportEntry report ) + { + events.add( SET_COMPLETED ); + data.add( report ); + } + + public void testStarting( ReportEntry report ) + { + events.add( TEST_STARTING ); + data.add( report ); + } + + public void testSucceeded( ReportEntry report ) + { + events.add( TEST_SUCCEEDED ); + testSucceeded.incrementAndGet(); + data.add( report ); + } + + public void testError( ReportEntry report ) + { + events.add( TEST_ERROR ); + data.add( report ); + testFailed.incrementAndGet(); + } + + public void testFailed( ReportEntry report ) + { + events.add( TEST_FAILED ); + data.add( report ); + testFailed.incrementAndGet(); + } + + public void testSkipped( ReportEntry report ) + { + events.add( TEST_SKIPPED ); + data.add( report ); + testIgnored.incrementAndGet(); + } + + public void testExecutionSkippedByUser() + { + } + + public void testSkippedByUser( ReportEntry report ) + { + testSkipped( report ); + } + + public List<String> getEvents() + { + return events; + } + + public List getData() + { + return data; + } + + public String getFirstEvent() + { + return events.get( 0 ); + } + + public ReportEntry getFirstData() + { + return (ReportEntry) data.get( 0 ); + } + + public void testAssumptionFailure( ReportEntry report ) + { + events.add( TEST_ASSUMPTION_FAIL ); + data.add( report ); + testIgnored.incrementAndGet(); + } + + public void info( String message ) + { + events.add( CONSOLE_OUTPUT ); + data.add( message ); + } + + public void writeTestOutput( byte[] buf, int off, int len, boolean stdout ) + { + events.add( stdout ? STDOUT : STDERR ); + data.add( new String( buf, off, len ) ); + } +}