[SUREFIRE-524] Forked Process not terminated if maven process aborted. Provide means to clean up.
Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/cb97ba70 Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/cb97ba70 Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/cb97ba70 Branch: refs/heads/master Commit: cb97ba70cb9ebe685f8f2a06e87b538795b5dd9b Parents: c8ddd6b Author: Tibor17 <[email protected]> Authored: Tue Sep 22 07:52:49 2015 +0200 Committer: Tibor17 <[email protected]> Committed: Tue Sep 22 07:52:49 2015 +0200 ---------------------------------------------------------------------- .../plugin/failsafe/IntegrationTestMojo.java | 18 +++ .../plugin/surefire/AbstractSurefireMojo.java | 15 ++- .../surefire/SurefireExecutionParameters.java | 2 + .../surefire/booterclient/BooterSerializer.java | 1 + .../surefire/booterclient/ForkStarter.java | 34 +++--- .../lazytestprovider/NotifiableTestStream.java | 4 +- .../lazytestprovider/TestLessInputStream.java | 15 +-- .../TestProvidingInputStream.java | 13 ++- ...erDeserializerProviderConfigurationTest.java | 2 +- ...terDeserializerStartupConfigurationTest.java | 2 +- .../TestLessInputStreamBuilderTest.java | 13 ++- .../maven/plugin/surefire/SurefirePlugin.java | 18 +++ .../src/site/apt/examples/shutdown.apt.vm | 78 +++++++++++++ maven-surefire-plugin/src/site/apt/index.apt.vm | 2 + maven-surefire-plugin/src/site/site.xml | 1 + .../surefire/booter/BaseProviderFactory.java | 14 ++- .../apache/maven/surefire/booter/Command.java | 33 +++++- .../surefire/booter/MasterProcessCommand.java | 12 +- .../surefire/booter/MasterProcessReader.java | 116 +++++++++++++++---- .../apache/maven/surefire/booter/Shutdown.java | 84 ++++++++++++++ .../maven/surefire/booter/ShutdownAware.java | 31 +++++ .../surefire/booter/SurefireReflector.java | 24 +++- .../providerapi/ProviderParameters.java | 3 + .../surefire/util/internal/StringUtils.java | 26 +++++ .../booter/MasterProcessCommandTest.java | 8 +- .../maven/surefire/booter/BooterConstants.java | 1 + .../surefire/booter/BooterDeserializer.java | 4 +- .../maven/surefire/booter/ForkedBooter.java | 30 ++--- .../surefire/booter/ProviderConfiguration.java | 14 ++- .../maven/surefire/booter/ProviderFactory.java | 1 + .../maven/surefire/junit4/JUnit4Provider.java | 4 +- .../surefire/junitcore/JUnitCoreProvider.java | 4 +- .../maven/surefire/testng/TestNGProvider.java | 4 +- 33 files changed, 539 insertions(+), 92 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 b359048..5e0c7ea 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 @@ -315,6 +315,19 @@ public class IntegrationTestMojo @Parameter( property = "failsafe.skipAfterFailureCount", defaultValue = "0" ) private int skipAfterFailureCount; + /** + * After the plugin process is shutdown by sending SIGTERM signal (CTRL+C), SHUTDOWN command is received by every + * forked JVM. By default (shutdown=testset) forked JVM would not continue with new test which means that + * the current test may still continue to run.<br/> + * The parameter can be configured with other two values "exit" and "kill".<br/> + * Using "exit" forked JVM executes System.exit(1) after the plugin process has received SIGTERM signal.<br/> + * Using "kill" the JVM executes Runtime.halt(1) and kills itself. + * + * @since 2.19 + */ + @Parameter( property = "failsafe.shutdown", defaultValue = "testset" ) + private String shutdown; + protected int getRerunFailingTestsCount() { return rerunFailingTestsCount; @@ -613,6 +626,11 @@ public class IntegrationTestMojo return skipAfterFailureCount; } + public String getShutdown() + { + return shutdown; + } + @Override public List<String> getIncludes() { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 ff2e51e..d67e29f 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 @@ -76,6 +76,7 @@ import org.apache.maven.surefire.booter.ClasspathConfiguration; import org.apache.maven.surefire.booter.KeyValueSource; import org.apache.maven.surefire.booter.ProviderConfiguration; import org.apache.maven.surefire.booter.ProviderParameterNames; +import org.apache.maven.surefire.booter.Shutdown; import org.apache.maven.surefire.booter.StartupConfiguration; import org.apache.maven.surefire.booter.SurefireBooterForkException; import org.apache.maven.surefire.booter.SurefireExecutionException; @@ -827,6 +828,7 @@ public abstract class AbstractSurefireMojo warnIfUselessUseSystemClassLoaderParameter(); warnIfDefunctGroupsCombinations(); warnIfRerunClashes(); + warnIfWrongShutdownValue(); } return true; } @@ -1467,7 +1469,8 @@ public abstract class AbstractSurefireMojo reporterConfiguration, testNg, // Not really used in provider. Limited to de/serializer. testSuiteDefinition, providerProperties, null, - false, cli, getSkipAfterFailureCount() ); + false, cli, getSkipAfterFailureCount(), + Shutdown.parameterOf( getShutdown() ) ); } private static Map<String, String> toStringProperties( Properties properties ) @@ -1973,6 +1976,7 @@ public abstract class AbstractSurefireMojo checksum.add( getTestSourceDirectory() ); checksum.add( getTest() ); checksum.add( getIncludes() ); + checksum.add( getShutdown() ); checksum.add( getExcludes() ); checksum.add( getLocalRepository() ); checksum.add( getSystemProperties() ); @@ -2365,6 +2369,15 @@ public abstract class AbstractSurefireMojo } } + private void warnIfWrongShutdownValue() + throws MojoFailureException + { + if ( !Shutdown.isKnown( getShutdown() ) ) + { + throw new MojoFailureException( "Parameter \"shutdown\" should have values " + Shutdown.listParameters() ); + } + } + final class TestNgProviderInfo implements ProviderInfo { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 6bd09ff..247f5e8 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 @@ -120,4 +120,6 @@ public interface SurefireExecutionParameters void setFailIfNoSpecifiedTests( boolean failIfNoSpecifiedTests ); int getSkipAfterFailureCount(); + + String getShutdown(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 139e14b..21807f5 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 @@ -134,6 +134,7 @@ class BooterSerializer properties.setProperty( FAILIFNOTESTS, String.valueOf( booterConfiguration.isFailIfNoTests() ) ); properties.setProperty( PROVIDER_CONFIGURATION, providerConfiguration.getProviderClassName() ); properties.setProperty( FAIL_FAST_COUNT, String.valueOf( booterConfiguration.getSkipAfterFailureCount() ) ); + properties.setProperty( SHUTDOWN, booterConfiguration.getShutdown().name() ); List<CommandLineOption> mainCliOptions = booterConfiguration.getMainCliOptions(); if ( mainCliOptions != null ) { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 39680b1..4742b7c 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 @@ -39,6 +39,7 @@ import org.apache.maven.surefire.booter.KeyValueSource; import org.apache.maven.surefire.booter.PropertiesWrapper; import org.apache.maven.surefire.booter.ProviderConfiguration; import org.apache.maven.surefire.booter.ProviderFactory; +import org.apache.maven.surefire.booter.Shutdown; import org.apache.maven.surefire.booter.StartupConfiguration; import org.apache.maven.surefire.booter.SurefireBooterForkException; import org.apache.maven.surefire.booter.SurefireExecutionException; @@ -109,9 +110,11 @@ public class ForkStarter { private static final long PING_IN_SECONDS = 10; - private static final ThreadFactory DAEMON_THREAD_FACTORY = newDaemonThreadFactory(); + private static final ThreadFactory FORKED_JVM_DAEMON_THREAD_FACTORY + = newDaemonThreadFactory( "surefire-fork-starter" ); - private static final ThreadFactory SHUTDOWN_HOOK_THREAD_FACTORY = newDaemonThreadFactory(); + private static final ThreadFactory SHUTDOWN_HOOK_THREAD_FACTORY + = newDaemonThreadFactory( "surefire-jvm-killer-shutdownhook" ); private final ScheduledExecutorService pingThreadScheduler = createPingScheduler(); @@ -207,7 +210,7 @@ public class ForkStarter new ForkClient( forkedReporterFactory, startupReportConfiguration.getTestVmSystemProperties() ); TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); TestLessInputStream stream = builder.build(); - Thread shutdown = createImmediateShutdownHookThread( builder ); + Thread shutdown = createImmediateShutdownHookThread( builder, providerConfiguration.getShutdown() ); ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder ); try { @@ -248,7 +251,7 @@ public class ForkStarter { ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>( forkCount ) ); - executorService.setThreadFactory( DAEMON_THREAD_FACTORY ); + executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY ); final Queue<String> tests = new ConcurrentLinkedQueue<String>(); @@ -265,7 +268,7 @@ public class ForkStarter } ScheduledFuture<?> ping = triggerPingTimerForShutdown( testStreams ); - Thread shutdown = createShutdownHookThread( testStreams ); + Thread shutdown = createShutdownHookThread( testStreams, providerConfiguration.getShutdown() ); try { @@ -328,10 +331,10 @@ public class ForkStarter ArrayList<Future<RunResult>> results = new ArrayList<Future<RunResult>>( 500 ); ThreadPoolExecutor executorService = new ThreadPoolExecutor( forkCount, forkCount, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>() ); - executorService.setThreadFactory( DAEMON_THREAD_FACTORY ); + executorService.setThreadFactory( FORKED_JVM_DAEMON_THREAD_FACTORY ); final TestLessInputStreamBuilder builder = new TestLessInputStreamBuilder(); ScheduledFuture<?> ping = triggerPingTimerForShutdown( builder ); - Thread shutdown = createCachableShutdownHookThread( builder ); + Thread shutdown = createCachableShutdownHookThread( builder, providerConfiguration.getShutdown() ); try { addShutDownHook( shutdown ); @@ -602,29 +605,32 @@ public class ForkStarter } } - private static Thread createImmediateShutdownHookThread( final TestLessInputStreamBuilder builder ) + private static Thread createImmediateShutdownHookThread( final TestLessInputStreamBuilder builder, + final Shutdown shutdownType ) { return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable() { public void run() { - builder.getImmediateCommands().shutdown(); + builder.getImmediateCommands().shutdown( shutdownType ); } } ); } - private static Thread createCachableShutdownHookThread( final TestLessInputStreamBuilder builder ) + private static Thread createCachableShutdownHookThread( final TestLessInputStreamBuilder builder, + final Shutdown shutdownType ) { return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable() { public void run() { - builder.getCachableCommands().shutdown(); + builder.getCachableCommands().shutdown( shutdownType ); } } ); } - private static Thread createShutdownHookThread( final Iterable<TestProvidingInputStream> streams ) + private static Thread createShutdownHookThread( final Iterable<TestProvidingInputStream> streams, + final Shutdown shutdownType ) { return SHUTDOWN_HOOK_THREAD_FACTORY.newThread( new Runnable() { @@ -632,7 +638,7 @@ public class ForkStarter { for ( TestProvidingInputStream stream : streams ) { - stream.shutdown(); + stream.shutdown( shutdownType ); } } } ); @@ -640,7 +646,7 @@ public class ForkStarter private static ScheduledExecutorService createPingScheduler() { - ThreadFactory threadFactory = newDaemonThreadFactory( "ping-thread-" + PING_IN_SECONDS + "sec" ); + ThreadFactory threadFactory = newDaemonThreadFactory( "ping-timer-" + PING_IN_SECONDS + "sec" ); return Executors.newScheduledThreadPool( 1, threadFactory ); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 d47645b..5c89173 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 @@ -19,6 +19,8 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider; * under the License. */ +import org.apache.maven.surefire.booter.Shutdown; + /** * Forked jvm notifies master process to provide a new test. * @@ -40,7 +42,7 @@ public interface NotifiableTestStream */ void skipSinceNextTest(); - void shutdown(); + void shutdown( Shutdown shutdownType ); void noop(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 index b15af89..04d39b3 100644 --- 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 @@ -20,6 +20,7 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider; */ import org.apache.maven.surefire.booter.Command; +import org.apache.maven.surefire.booter.Shutdown; import java.io.IOException; import java.util.Iterator; @@ -33,8 +34,8 @@ 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; +import static org.apache.maven.surefire.booter.Command.toShutdown; /** * Dispatches commands without tests. @@ -73,11 +74,11 @@ public final class TestLessInputStream } } - public void shutdown() + public void shutdown( Shutdown shutdownType ) { if ( canContinue() ) { - immediateCommands.add( SHUTDOWN ); + immediateCommands.add( toShutdown( shutdownType ) ); barrier.release(); } } @@ -336,7 +337,7 @@ public final class TestLessInputStream } } - public void shutdown() + public void shutdown( Shutdown shutdownType ) { Lock lock = rwLock.readLock(); lock.lock(); @@ -344,7 +345,7 @@ public final class TestLessInputStream { for ( TestLessInputStream aliveStream : TestLessInputStreamBuilder.this.aliveStreams ) { - aliveStream.shutdown(); + aliveStream.shutdown( shutdownType ); } } finally @@ -398,13 +399,13 @@ public final class TestLessInputStream } } - public void shutdown() + public void shutdown( Shutdown shutdownType ) { Lock lock = rwLock.readLock(); lock.lock(); try { - if ( TestLessInputStreamBuilder.this.addTailNodeIfAbsent( SHUTDOWN ) ) + if ( TestLessInputStreamBuilder.this.addTailNodeIfAbsent( toShutdown( shutdownType ) ) ) { release(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 dcc2997..766843d 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,6 +20,7 @@ package org.apache.maven.plugin.surefire.booterclient.lazytestprovider; */ import org.apache.maven.surefire.booter.Command; +import org.apache.maven.surefire.booter.Shutdown; import java.io.IOException; import java.util.Queue; @@ -27,11 +28,11 @@ 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.booter.Command.SKIP_SINCE_NEXT_TEST; -import static org.apache.maven.surefire.booter.Command.SHUTDOWN; import static org.apache.maven.surefire.booter.Command.NOOP; +import static org.apache.maven.surefire.booter.Command.SKIP_SINCE_NEXT_TEST; +import static org.apache.maven.surefire.booter.Command.toRunClass; +import static org.apache.maven.surefire.booter.Command.toShutdown; /** * An {@link java.io.InputStream} that, when read, provides test class names out of a queue. @@ -90,11 +91,11 @@ public final class TestProvidingInputStream } } - public void shutdown() + public void shutdown( Shutdown shutdownType ) { if ( canContinue() ) { - commands.add( SHUTDOWN ); + commands.add( toShutdown( shutdownType ) ); barrier.release(); } } @@ -115,7 +116,7 @@ public final class TestProvidingInputStream if ( cmd == null ) { String cmdData = testClassNames.poll(); - return cmdData == null ? Command.TEST_SET_FINISHED : new Command( RUN_CLASS, cmdData ); + return cmdData == null ? Command.TEST_SET_FINISHED : toRunClass( cmdData ); } else { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 9c8cc07..623edf3 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, 0 ); + readTestsFromInStream, cli, 0, Shutdown.DEFAULT ); } private StartupConfiguration getTestStartupConfiguration( ClassLoaderConfiguration classLoaderConfiguration ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 6f232fa..007640d 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, 0 ); + BooterDeserializerProviderConfigurationTest.aTestTyped, true, cli, 0, Shutdown.DEFAULT ); } private StartupConfiguration getTestStartupConfiguration( ClassLoaderConfiguration classLoaderConfiguration ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java index c1a075f..6dee0a4 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/lazytestprovider/TestLessInputStreamBuilderTest.java @@ -30,8 +30,9 @@ import java.util.NoSuchElementException; import static org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStream.TestLessInputStreamBuilder; 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; +import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN; +import static org.apache.maven.surefire.booter.Shutdown.EXIT; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.hamcrest.MatcherAssert.assertThat; @@ -103,24 +104,24 @@ public class TestLessInputStreamBuilderTest assertThat( is.availablePermits(), is( 1 ) ); builder.getCachableCommands().skipSinceNextTest(); assertThat( is.availablePermits(), is( 1 ) ); - builder.getImmediateCommands().shutdown(); + builder.getImmediateCommands().shutdown( EXIT ); assertThat( is.availablePermits(), is( 2 ) ); is.beforeNextCommand(); - assertThat( is.nextCommand(), is( SHUTDOWN ) ); + assertThat( is.nextCommand().getCommandType(), is( SHUTDOWN ) ); assertThat( is.availablePermits(), is( 1 ) ); is.beforeNextCommand(); assertThat( is.nextCommand(), is( SKIP_SINCE_NEXT_TEST ) ); assertThat( is.availablePermits(), is( 0 ) ); builder.getImmediateCommands().noop(); assertThat( is.availablePermits(), is( 1 ) ); - builder.getCachableCommands().shutdown(); - builder.getCachableCommands().shutdown(); + builder.getCachableCommands().shutdown( EXIT ); + builder.getCachableCommands().shutdown( EXIT ); assertThat( is.availablePermits(), is( 2 ) ); is.beforeNextCommand(); assertThat( is.nextCommand(), is( NOOP ) ); assertThat( is.availablePermits(), is( 1 ) ); is.beforeNextCommand(); - assertThat( is.nextCommand(), is( SHUTDOWN ) ); + assertThat( is.nextCommand().getCommandType(), is( SHUTDOWN ) ); assertThat( is.availablePermits(), is( 0 ) ); e.expect( NoSuchElementException.class ); is.nextCommand(); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java index b26b493..88bb107 100644 --- a/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java +++ b/maven-surefire-plugin/src/main/java/org/apache/maven/plugin/surefire/SurefirePlugin.java @@ -292,6 +292,19 @@ public class SurefirePlugin @Parameter( property = "surefire.skipAfterFailureCount", defaultValue = "0" ) private int skipAfterFailureCount; + /** + * After the plugin process is shutdown by sending SIGTERM signal (CTRL+C), SHUTDOWN command is received by every + * forked JVM. By default (shutdown=testset) forked JVM would not continue with new test which means that + * the current test may still continue to run.<br/> + * The parameter can be configured with other two values "exit" and "kill".<br/> + * Using "exit" forked JVM executes System.exit(1) after the plugin process has received SIGTERM signal.<br/> + * Using "kill" the JVM executes Runtime.halt(1) and kills itself. + * + * @since 2.19 + */ + @Parameter( property = "surefire.shutdown", defaultValue = "testset" ) + private String shutdown; + protected int getRerunFailingTestsCount() { return rerunFailingTestsCount; @@ -466,6 +479,11 @@ public class SurefirePlugin return skipAfterFailureCount; } + public String getShutdown() + { + return shutdown; + } + public boolean isPrintSummary() { return printSummary; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm new file mode 100644 index 0000000..f50710b --- /dev/null +++ b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm @@ -0,0 +1,78 @@ + ------ + Forked JVM Shutdown + ------ + Tibor Digana <[email protected]> + ------ + September 2015 + ------ + + ~~ 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. + + ~~ NOTE: For help with the syntax of this file, see: + ~~ http://maven.apache.org/doxia/references/apt-format.html + +Shutdown of Forked JVM + +* Embedded shutdown + + After the test-set has completed, the process executes + <<<java.lang.System.exit(0)>>> which starts shutdown hooks. At this point + the process may run next 30 seconds until all non daemon Threads die. + After the period of time has elapsed, the process kills itself by + <<<java.lang.Runtime.halt(0)>>>. + +* Pinging forked JVM + + The master process sends NOOP command to a forked JVM every 10 seconds. + Forked JVM is waiting for the command every 20 seconds. + If the master process is killed (received SIGKILL signal) or shutdown + (pressed CTRL+C, received SIGTERM signal), forked JVM terminates after + timing out waiting period. + +* Shutdown of forked JVM by stopping the build + + After the master process of the build is shutdown by sending SIGTERM signal + or pressing CTRL+C, the master process sends SHUTDOWN command to every + forked JVM. By default (configuration parameter <<<shutdown=testset>>>) + forked JVM would not continue with new test which means that the current + test may still continue to run. + The parameter <<<shutdown>>> can be configured with other two values + <<<exit>>> and <<<kill>>>. Using <<<exit>>> forked JVM executes + <<<java.lang.System.exit(1)>>> after the master process has received SIGTERM. + Using <<<kill>>> the JVM executes <<<java.lang.Runtime.halt(1)>>>, example: + ++---+ +<project> + [...] + <build> + <plugins> + <plugin> + <groupId>${project.groupId}</groupId> + <artifactId>${project.artifactId}</artifactId> + <version>${project.version}</version> + <configuration> + <shutdown>kill</shutdown> + </configuration> + </plugin> + </plugins> + </build> + [...] +</project> ++---+ + + \ No newline at end of file http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/maven-surefire-plugin/src/site/apt/index.apt.vm ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/src/site/apt/index.apt.vm b/maven-surefire-plugin/src/site/apt/index.apt.vm index c9f8c9e..09ea2c0 100644 --- a/maven-surefire-plugin/src/site/apt/index.apt.vm +++ b/maven-surefire-plugin/src/site/apt/index.apt.vm @@ -153,4 +153,6 @@ mvn verify * {{{./examples/fork-options-and-parallel-execution.html}Fork Options and Parallel Test Execution}} + * {{{./examples/shutdown.html}Shutdown of Forked JVM}} + [] http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/maven-surefire-plugin/src/site/site.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/src/site/site.xml b/maven-surefire-plugin/src/site/site.xml index 833c0a9..b3e9cd2 100644 --- a/maven-surefire-plugin/src/site/site.xml +++ b/maven-surefire-plugin/src/site/site.xml @@ -54,6 +54,7 @@ <item name="New Error Summary" href="newerrorsummary.html"/> <item name="Fork Options and Parallel Test Execution" href="examples/fork-options-and-parallel-execution.html"/> <item name="Using Console Logs" href="examples/logging.html"/> + <item name="Shutdown of Forked JVM" href="examples/shutdown.html"/> </menu> </body> </project> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java index 0433a01..b33b5d0 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/BaseProviderFactory.java @@ -46,7 +46,7 @@ import java.util.Map; public class BaseProviderFactory implements DirectoryScannerParametersAware, ReporterConfigurationAware, SurefireClassLoadersAware, TestRequestAware, ProviderPropertiesAware, ProviderParameters, TestArtifactInfoAware, RunOrderParametersAware, MainCliOptionsAware, - FailFastAware + FailFastAware, ShutdownAware { private static final int ROOT_CHANNEL = 0; @@ -72,6 +72,8 @@ public class BaseProviderFactory private int skipAfterFailureCount; + private Shutdown shutdown; + public BaseProviderFactory( ReporterFactory reporterFactory, boolean insideFork ) { this.reporterFactory = reporterFactory; @@ -206,4 +208,14 @@ public class BaseProviderFactory { return insideFork; } + + public Shutdown getShutdown() + { + return shutdown; + } + + public void setShutdown( Shutdown shutdown ) + { + this.shutdown = shutdown; + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 index 25bb2d1..070980c 100644 --- 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 @@ -20,6 +20,10 @@ package org.apache.maven.surefire.booter; */ import static org.apache.maven.surefire.util.internal.StringUtils.requireNonNull; +import static org.apache.maven.surefire.util.internal.StringUtils.isBlank; +import static org.apache.maven.surefire.booter.MasterProcessCommand.RUN_CLASS; +import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN; +import static org.apache.maven.surefire.booter.Shutdown.DEFAULT; /** * Encapsulates data and command sent from master to forked process. @@ -31,7 +35,6 @@ public final class Command { public static final Command TEST_SET_FINISHED = new Command( MasterProcessCommand.TEST_SET_FINISHED ); public static final Command SKIP_SINCE_NEXT_TEST = new Command( MasterProcessCommand.SKIP_SINCE_NEXT_TEST ); - public static final Command SHUTDOWN = new Command( MasterProcessCommand.SHUTDOWN ); public static final Command NOOP = new Command( MasterProcessCommand.NOOP ); private final MasterProcessCommand command; @@ -43,6 +46,16 @@ public final class Command this.data = data; } + public static Command toShutdown( Shutdown shutdownType ) + { + return new Command( SHUTDOWN, shutdownType.name() ); + } + + public static Command toRunClass( String runClass ) + { + return new Command( RUN_CLASS, runClass ); + } + public Command( MasterProcessCommand command ) { this( command, null ); @@ -58,6 +71,24 @@ public final class Command return data; } + /** + * @return {@link Shutdown} or {@link Shutdown#DEFAULT} if {@link #getData()} is null or blank string + * @throws IllegalArgumentException if string data {@link #getData()} is not applicable to enum {@link Shutdown} + */ + public Shutdown toShutdownData() + { + if ( !isType( SHUTDOWN ) ) + { + throw new IllegalStateException( "expected MasterProcessCommand.SHUTDOWN" ); + } + return isBlank( data ) ? DEFAULT : Shutdown.valueOf( data ); + } + + public boolean isType( MasterProcessCommand command ) + { + return command == this.command; + } + @Override public boolean equals( Object o ) { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 index 2129bf0..8059537 100644 --- 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 @@ -19,10 +19,13 @@ package org.apache.maven.surefire.booter; * under the License. */ +import org.apache.maven.surefire.util.internal.StringUtils; + import java.io.DataInputStream; import java.io.EOFException; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; import static org.apache.maven.surefire.util.internal.StringUtils.FORK_STREAM_CHARSET_NAME; import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringForForkCommunication; @@ -41,10 +44,13 @@ public enum MasterProcessCommand RUN_CLASS( 0, String.class ), TEST_SET_FINISHED( 1, Void.class ), SKIP_SINCE_NEXT_TEST( 2, Void.class ), - SHUTDOWN( 3, Void.class ), + SHUTDOWN( 3, Shutdown.class ), + /** To tell a forked process that the master process is still alive. Repeated after 10 seconds. */ NOOP( 4, Void.class ); + private static final Charset ASCII = Charset.forName( "ASCII" ); + private final int id; private final Class<?> dataType; @@ -157,6 +163,8 @@ public enum MasterProcessCommand { case RUN_CLASS: return new String( data, FORK_STREAM_CHARSET_NAME ); + case SHUTDOWN: + return StringUtils.decode( data, ASCII ); default: return null; } @@ -173,6 +181,8 @@ public enum MasterProcessCommand { case RUN_CLASS: return encodeStringForForkCommunication( data ); + case SHUTDOWN: + return StringUtils.encode( data, ASCII ); default: return new byte[0]; } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java index ab45d81..f2f783c 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/MasterProcessReader.java @@ -37,14 +37,13 @@ import static java.lang.Thread.State.RUNNABLE; import static java.lang.Thread.State.TERMINATED; import static java.util.concurrent.locks.LockSupport.park; import static java.util.concurrent.locks.LockSupport.unpark; -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.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.util.internal.DaemonThreadFactory.newDaemonThread; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_NEXT_TEST; +import static org.apache.maven.surefire.booter.Shutdown.DEFAULT; /** * Reader of commands coming from plugin(master) process. @@ -65,11 +64,13 @@ public final class MasterProcessReader private final Queue<Thread> waiters = new ConcurrentLinkedQueue<Thread>(); - private final Node head = new Node(); - private final CountDownLatch startMonitor = new CountDownLatch( 1 ); - private volatile Node tail = head; + private final Node headTestClassQueue = new Node(); + + private volatile Node tailTestClassQueue = headTestClassQueue; + + private volatile Shutdown shutdown; private static class Node { @@ -87,6 +88,12 @@ public final class MasterProcessReader return reader; } + public MasterProcessReader setShutdown( Shutdown shutdown ) + { + this.shutdown = shutdown; + return this; + } + public boolean awaitStarted() throws TestSetFailedException { @@ -136,7 +143,7 @@ public final class MasterProcessReader Iterable<String> getIterableClasses( PrintStream originalOutStream ) { - return new ClassesIterable( head, originalOutStream ); + return new ClassesIterable( headTestClassQueue, originalOutStream ); } public void stop() @@ -149,6 +156,11 @@ public final class MasterProcessReader } } + private boolean isStopped() + { + return state.get() == TERMINATED; + } + private static boolean isLastNode( Node current ) { return current.successor.get() == current; @@ -156,17 +168,20 @@ public final class MasterProcessReader private boolean isQueueFull() { - return isLastNode( tail ); + return isLastNode( tailTestClassQueue ); } - private boolean addToQueue( String item ) + /** + * thread-safety: Must be called from single thread like here the reader thread only. + */ + private boolean addTestClassToQueue( String item ) { - if ( tail.item == null ) + if ( tailTestClassQueue.item == null ) { - tail.item = item; + tailTestClassQueue.item = item; Node newNode = new Node(); - tail.successor.set( newNode ); - tail = newNode; + tailTestClassQueue.successor.set( newNode ); + tailTestClassQueue = newNode; return true; } else @@ -183,18 +198,21 @@ public final class MasterProcessReader public void makeQueueFull() { // order between (#compareAndSet, and #get) matters in multithreading - for ( Node tail = this.tail; + for ( Node tail = this.tailTestClassQueue; !tail.successor.compareAndSet( null, tail ) && tail.successor.get() != tail; tail = tail.successor.get() ); } + /** + * thread-safety: Must be called from single thread like here the reader thread only. + */ private void insertForQueue( Command cmd ) { MasterProcessCommand expectedCommandType = cmd.getCommandType(); switch ( expectedCommandType ) { case RUN_CLASS: - addToQueue( cmd.getData() ); + addTestClassToQueue( cmd.getData() ); break; case TEST_SET_FINISHED: makeQueueFull(); @@ -220,6 +238,9 @@ public final class MasterProcessReader } } + /** + * thread-safety: Must be called from single thread like here the reader thread only. + */ private void insert( Command cmd ) { insertForQueue( cmd ); @@ -292,7 +313,7 @@ public final class MasterProcessReader private void popUnread() { - if ( state.get() == TERMINATED ) + if ( isStopped() ) { clazz = null; return; @@ -315,7 +336,13 @@ public final class MasterProcessReader /** * {@link java.util.concurrent.locks.LockSupport#park()} * may spuriously (that is, for no reason) return, therefore the loop here. + * Could be waken up by System.exit or closing the stream. */ + if ( isStopped() ) + { + clazz = null; + return; + } } while ( current.item == null && !isLastNode( current ) ); clazz = current.item; current = current.successor.get(); @@ -329,7 +356,7 @@ public final class MasterProcessReader while ( tryNullWhiteClass() ); } - if ( state.get() == TERMINATED ) + if ( isStopped() ) { clazz = null; } @@ -355,6 +382,9 @@ public final class MasterProcessReader } } + /** + * thread-safety: Must be called from single thread like here the reader thread only. + */ private Command read( DataInputStream stdIn ) throws IOException { @@ -408,15 +438,24 @@ public final class MasterProcessReader } else { - if ( command.getCommandType() == TEST_SET_FINISHED ) - { - isTestSetFinished = true; - wakeupWaiters(); - } - else if ( command.getCommandType() == RUN_CLASS ) + switch ( command.getCommandType() ) { - wakeupWaiters(); + case TEST_SET_FINISHED: + isTestSetFinished = true; + wakeupWaiters(); + break; + case RUN_CLASS: + wakeupWaiters(); + break; + case SHUTDOWN: + insertForQueue( Command.TEST_SET_FINISHED ); + wakeupWaiters(); + break; + default: + // checkstyle do nothing + break; } + insertForListeners( command ); } } @@ -424,10 +463,16 @@ public final class MasterProcessReader catch ( EOFException e ) { MasterProcessReader.this.state.set( TERMINATED ); + if ( !isTestSetFinished ) + { + exitByConfiguration(); + // does not go to finally + } } catch ( IOException e ) { MasterProcessReader.this.state.set( TERMINATED ); + // If #stop() method is called, reader thread is interrupted and cause is InterruptedException. if ( !( e.getCause() instanceof InterruptedException ) ) { System.err.println( "[SUREFIRE] std/in stream corrupted" ); @@ -439,10 +484,33 @@ public final class MasterProcessReader // ensure fail-safe iterator as well as safe to finish in for-each loop using ClassesIterator if ( !isTestSetFinished ) { - insert( new Command( TEST_SET_FINISHED ) ); + insert( Command.TEST_SET_FINISHED ); } wakeupWaiters(); } } + + /** + * thread-safety: Must be called from single thread like here the reader thread only. + */ + private void exitByConfiguration() + { + Shutdown shutdown = MasterProcessReader.this.shutdown; // won't read inconsistent changes through the stack + if ( shutdown != null && shutdown != DEFAULT ) + { + insert( Command.TEST_SET_FINISHED ); // lazily + wakeupWaiters(); + switch ( shutdown ) + { + case EXIT: + System.exit( 1 ); + case KILL: + Runtime.getRuntime().halt( 1 ); + default: + // should not happen; otherwise you missed enum case + break; + } + } + } } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java new file mode 100644 index 0000000..660a40e --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/Shutdown.java @@ -0,0 +1,84 @@ +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. + */ + +/** + * Specifies the way how the forked jvm should be terminated after + * class AbstractCommandStream is closed and CTRL+C. + * + * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +public enum Shutdown +{ + DEFAULT( "testset" ), + EXIT( "exit" ), + KILL( "kill" ); + + private final String param; + + Shutdown( String param ) + { + this.param = param; + } + + public String getParam() + { + return param; + } + + public static boolean isKnown( String param ) + { + for ( Shutdown shutdown : values() ) + { + if ( shutdown.param.equals( param ) ) + { + return true; + } + } + return false; + } + + public static String listParameters() + { + String values = ""; + for ( Shutdown shutdown : values() ) + { + if ( values.length() != 0 ) + { + values += ", "; + } + values += shutdown.getParam(); + } + return values; + } + + public static Shutdown parameterOf( String parameter ) + { + for ( Shutdown shutdown : values() ) + { + if ( shutdown.param.equals( parameter ) ) + { + return shutdown; + } + } + return DEFAULT; + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java new file mode 100644 index 0000000..a843b27 --- /dev/null +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/ShutdownAware.java @@ -0,0 +1,31 @@ +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. + */ + +/** + * See the plugin configuration parameter <em>shutdown</em>. + * + * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> + * @since 2.19 + */ +public interface ShutdownAware +{ + void setShutdown( Shutdown shutdown ); +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java index cf07331..96bc3c0 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/SurefireReflector.java @@ -90,6 +90,10 @@ public class SurefireReflector private final Class<Enum> commandLineOptionsClass; + private final Class<?> shutdownAwareClass; + + private final Class<Enum> shutdownClass; + @SuppressWarnings( "unchecked" ) public SurefireReflector( ClassLoader surefireClassLoader ) @@ -114,8 +118,9 @@ public class SurefireReflector booterParameters = surefireClassLoader.loadClass( ProviderParameters.class.getName() ); testListResolver = surefireClassLoader.loadClass( TestListResolver.class.getName() ); mainCliOptions = surefireClassLoader.loadClass( MainCliOptionsAware.class.getName() ); - commandLineOptionsClass = - (Class<Enum>) surefireClassLoader.loadClass( CommandLineOption.class.getName() ); + commandLineOptionsClass = (Class<Enum>) surefireClassLoader.loadClass( CommandLineOption.class.getName() ); + shutdownAwareClass = surefireClassLoader.loadClass( ShutdownAware.class.getName() ); + shutdownClass = (Class<Enum>) surefireClassLoader.loadClass( Shutdown.class.getName() ); } catch ( ClassNotFoundException e ) { @@ -299,6 +304,21 @@ public class SurefireReflector ReflectionUtils.invokeSetter( o, "setSkipAfterFailureCount", int.class, skipAfterFailureCount ); } + public void setShutdown( Object o, Shutdown shutdown ) + { + if ( shutdownAwareClass.isAssignableFrom( o.getClass() ) ) + { + for ( Enum e : shutdownClass.getEnumConstants() ) + { + if ( shutdown.ordinal() == e.ordinal() ) + { + ReflectionUtils.invokeSetter( o, "setShutdown", shutdownClass, e ); + break; + } + } + } + } + public void setDirectoryScannerParameters( Object o, DirectoryScannerParameters dirScannerParams ) { final Object param = createDirectoryScannerParameters( dirScannerParams ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java index 708b436..b6e6467 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/providerapi/ProviderParameters.java @@ -19,6 +19,7 @@ package org.apache.maven.surefire.providerapi; * under the License. */ +import org.apache.maven.surefire.booter.Shutdown; import org.apache.maven.surefire.cli.CommandLineOption; import org.apache.maven.surefire.report.ConsoleLogger; import org.apache.maven.surefire.report.ReporterConfiguration; @@ -143,4 +144,6 @@ public interface ProviderParameters * in-plugin provider. */ boolean isInsideFork(); + + Shutdown getShutdown(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 043b87b..06cd55e 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 @@ -339,6 +339,32 @@ public class StringUtils return ByteBuffer.wrap( out, 0, outPos ); } + public static String decode( byte[] toDecode, Charset charset ) + { + try + { + // @todo use new JDK 1.6 constructor String(byte bytes[], Charset charset) + return new String( toDecode, charset.name() ); + } + catch ( UnsupportedEncodingException e ) + { + throw new RuntimeException( "The JVM must support Charset " + charset, e ); + } + } + + public static byte[] encode( String toEncode, Charset charset ) + { + try + { + // @todo use new JDK 1.6 method getBytes(Charset charset) + return toEncode.getBytes( charset.name() ); + } + catch ( UnsupportedEncodingException e ) + { + throw new RuntimeException( "The JVM must support Charset " + charset, e ); + } + } + public static byte[] encodeStringForForkCommunication( String string ) { try http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 index 717ab4e..f7f7767 100644 --- 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 @@ -105,11 +105,11 @@ public class MasterProcessCommandTest assertNull( decoded ); break; case SHUTDOWN: - assertEquals( Void.class, command.getDataType() ); - encoded = command.fromDataType( dummyData ); - assertThat( encoded.length, is( 0 ) ); + assertEquals( Shutdown.class, command.getDataType() ); + encoded = command.fromDataType( Shutdown.EXIT.name() ); + assertThat( encoded.length, is( 4 ) ); decoded = command.toDataTypeAsString( encoded ); - assertNull( decoded ); + assertThat( decoded, is( Shutdown.EXIT.name() ) ); break; case NOOP: assertEquals( Void.class, command.getDataType() ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java index 749f0e9..d310b9a 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterConstants.java @@ -54,4 +54,5 @@ public final class BooterConstants public static final String RERUN_FAILING_TESTS_COUNT = "rerunFailingTestsCount"; public static final String MAIN_CLI_OPTIONS = "mainCliOptions"; public static final String FAIL_FAST_COUNT = "failFastCount"; + public static final String SHUTDOWN = "shutdown"; } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java index b8f2d0b..2540a7f 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/BooterDeserializer.java @@ -102,10 +102,12 @@ public class BooterDeserializer int failFastCount = properties.getIntProperty( FAIL_FAST_COUNT ); + Shutdown shutdown = Shutdown.valueOf( properties.getProperty( SHUTDOWN ) ); + return new ProviderConfiguration( dirScannerParams, runOrderParameters, properties.getBooleanProperty( FAILIFNOTESTS ), reporterConfiguration, testNg, testSuiteDefinition, properties.getProperties(), typeEncodedTestForFork, - preferTestsFromInStream, fromStrings( cli ), failFastCount ); + preferTestsFromInStream, fromStrings( cli ), failFastCount, shutdown ); } public StartupConfiguration getProviderConfiguration() http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 c7746e9..bf58ca9 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 @@ -40,6 +40,8 @@ import org.apache.maven.surefire.testset.TestSetFailedException; import static org.apache.maven.surefire.booter.MasterProcessCommand.SHUTDOWN; import static org.apache.maven.surefire.booter.MasterProcessCommand.NOOP; +import static org.apache.maven.surefire.booter.Shutdown.DEFAULT; +import static org.apache.maven.surefire.booter.Shutdown.KILL; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR; import static org.apache.maven.surefire.booter.ForkingRunListener.encode; @@ -138,7 +140,7 @@ public final class ForkedBooter encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut ); originalOut.flush(); // noinspection CallToSystemExit - exit( 0, false ); + exit( 0, DEFAULT ); } catch ( Throwable t ) { @@ -146,7 +148,7 @@ public final class ForkedBooter // noinspection UseOfSystemOutOrSystemErr t.printStackTrace( System.err ); // noinspection ProhibitedExceptionThrown,CallToSystemExit - exit( 1, false ); + exit( 1, DEFAULT ); } finally { @@ -180,7 +182,7 @@ public final class ForkedBooter { public void update( Command command ) { - exit( 1, true ); + exit( 1, command.toShutdownData() ); } }; } @@ -194,7 +196,7 @@ public final class ForkedBooter boolean hasPing = pingDone.getAndSet( false ); if ( !hasPing ) { - exit( 1, true ); + exit( 1, KILL ); } } }; @@ -206,18 +208,19 @@ public final class ForkedBooter out.write( encodeBytes, 0, encodeBytes.length ); } - private static void exit( int returnCode, boolean immediate ) + private static void exit( int returnCode, Shutdown shutdownType ) { MasterProcessReader.getReader().stop(); - - if ( immediate ) - { - Runtime.getRuntime().halt( returnCode ); - } - else + switch ( shutdownType ) { - launchLastDitchDaemonShutdownThread( returnCode ); - System.exit( returnCode ); + case KILL: + Runtime.getRuntime().halt( returnCode ); + case EXIT: + launchLastDitchDaemonShutdownThread( returnCode ); + System.exit( returnCode ); + case DEFAULT: + default: + break; } } @@ -303,6 +306,7 @@ public final class ForkedBooter bpf.setDirectoryScannerParameters( providerConfiguration.getDirScannerParams() ); bpf.setMainCliOptions( providerConfiguration.getMainCliOptions() ); bpf.setSkipAfterFailureCount( providerConfiguration.getSkipAfterFailureCount() ); + bpf.setShutdown( providerConfiguration.getShutdown() ); String providerClass = startupConfiguration1.getActualClassName(); return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf ); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java index 69c7f2c..f4a3b42 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderConfiguration.java @@ -68,13 +68,16 @@ public class ProviderConfiguration private int skipAfterFailureCount; + private Shutdown shutdown; + @SuppressWarnings( "checkstyle:parameternumber" ) public ProviderConfiguration( DirectoryScannerParameters directoryScannerParameters, RunOrderParameters runOrderParameters, boolean failIfNoTests, ReporterConfiguration reporterConfiguration, TestArtifactInfo testArtifact, TestRequest testSuiteDefinition, Map<String, String> providerProperties, TypeEncodedValue typeEncodedTestSet, boolean readTestsFromInStream, - List<CommandLineOption> mainCliOptions, int skipAfterFailureCount ) + List<CommandLineOption> mainCliOptions, int skipAfterFailureCount, + Shutdown shutdown ) { this.runOrderParameters = runOrderParameters; this.providerProperties = providerProperties; @@ -87,6 +90,7 @@ public class ProviderConfiguration this.readTestsFromInStream = readTestsFromInStream; this.mainCliOptions = mainCliOptions; this.skipAfterFailureCount = skipAfterFailureCount; + this.shutdown = shutdown; } public ReporterConfiguration getReporterConfiguration() @@ -156,13 +160,13 @@ public class ProviderConfiguration return mainCliOptions; } - public void setSkipAfterFailureCount( int skipAfterFailureCount ) + public int getSkipAfterFailureCount() { - this.skipAfterFailureCount = skipAfterFailureCount; + return skipAfterFailureCount; } - public int getSkipAfterFailureCount() + public Shutdown getShutdown() { - return skipAfterFailureCount; + return shutdown; } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java index 5ad587f..85f9c45 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProviderFactory.java @@ -100,6 +100,7 @@ public class ProviderFactory surefireReflector.setIfDirScannerAware( o, providerConfiguration.getDirScannerParams() ); surefireReflector.setMainCliOptions( o, providerConfiguration.getMainCliOptions() ); surefireReflector.setSkipAfterFailureCount( o, providerConfiguration.getSkipAfterFailureCount() ); + surefireReflector.setShutdown( o, providerConfiguration.getShutdown() ); Object provider = surefireReflector.instantiateProvider( startupConfiguration.getActualClassName(), o ); currentThread.setContextClassLoader( systemClassLoader ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/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 3321a82..045b194 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 @@ -101,7 +101,9 @@ public class JUnit4Provider public JUnit4Provider( ProviderParameters booterParameters ) { // don't start a thread in MasterProcessReader while we are in in-plugin process - commandsReader = booterParameters.isInsideFork() ? MasterProcessReader.getReader() : null; + commandsReader = booterParameters.isInsideFork() + ? MasterProcessReader.getReader().setShutdown( booterParameters.getShutdown() ) + : null; providerParameters = booterParameters; testClassLoader = booterParameters.getTestClassLoader(); scanResult = booterParameters.getScanResult(); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java ---------------------------------------------------------------------- diff --git a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java index 2c7d5c9..799c211 100644 --- a/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java +++ b/surefire-providers/surefire-junit47/src/main/java/org/apache/maven/surefire/junitcore/JUnitCoreProvider.java @@ -89,7 +89,9 @@ public class JUnitCoreProvider public JUnitCoreProvider( ProviderParameters providerParameters ) { - commandsReader = providerParameters.isInsideFork() ? MasterProcessReader.getReader() : null; + commandsReader = providerParameters.isInsideFork() + ? MasterProcessReader.getReader().setShutdown( providerParameters.getShutdown() ) + : null; this.providerParameters = providerParameters; testClassLoader = providerParameters.getTestClassLoader(); scanResult = providerParameters.getScanResult(); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/cb97ba70/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java ---------------------------------------------------------------------- diff --git a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java index a317042..c2a1018 100644 --- a/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java +++ b/surefire-providers/surefire-testng/src/main/java/org/apache/maven/surefire/testng/TestNGProvider.java @@ -73,7 +73,9 @@ public class TestNGProvider public TestNGProvider( ProviderParameters booterParameters ) { // don't start a thread in MasterProcessReader while we are in in-plugin process - commandsReader = booterParameters.isInsideFork() ? MasterProcessReader.getReader() : null; + commandsReader = booterParameters.isInsideFork() + ? MasterProcessReader.getReader().setShutdown( booterParameters.getShutdown() ) + : null; providerParameters = booterParameters; testClassLoader = booterParameters.getTestClassLoader(); runOrderCalculator = booterParameters.getRunOrderCalculator();
