This is an automated email from the ASF dual-hosted git repository. tibordigana pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit bca90b23e264bb21db9c447f22f039d0cb5a0c1f Author: tibordigana <[email protected]> AuthorDate: Sat Jul 20 14:55:27 2019 +0200 [SUREFIRE-1675] Forked JVM terminates with 'halt' when another module's tests fail --- Jenkinsfile | 12 +- maven-failsafe-plugin/pom.xml | 2 +- .../maven/surefire/booter/CommandReader.java | 3 +- .../apache/maven/surefire/booter/ForkedBooter.java | 134 +++++++++++++++---- .../surefire/booter/ForkedBooterMockTest.java | 134 +++++++++++++++++++ .../maven/surefire/booter/ForkedBooterTest.java | 144 +++++++++++++++++++++ .../maven/surefire/booter/JUnit4SuiteTest.java | 2 + ...Surefire946KillMainProcessInReusableForkIT.java | 118 +++++++++++++---- .../surefire-946-dummy-dependency/pom.xml | 39 ++++++ .../src/main/java/dummy/DummyClass.java} | 31 ++--- .../pom.xml | 7 + .../test/java/junit44/environment/Basic01Test.java | 2 + .../test/java/junit44/environment/Basic02Test.java | 2 + .../test/java/junit44/environment/Basic03Test.java | 2 + .../test/java/junit44/environment/Basic04Test.java | 2 + .../test/java/junit44/environment/Basic05Test.java | 2 + .../test/java/junit44/environment/Basic06Test.java | 2 + .../test/java/junit44/environment/Basic07Test.java | 2 + .../test/java/junit44/environment/Basic08Test.java | 2 + .../test/java/junit44/environment/Basic09Test.java | 2 + .../test/java/junit44/environment/Basic10Test.java | 2 + .../surefire-946-self-destruct-plugin/pom.xml | 6 - 22 files changed, 570 insertions(+), 82 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 175c763..0abf4ba 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -30,10 +30,10 @@ properties( ] ) -final def oses = ['linux':'ubuntu && !H24', 'windows':'Windows && !windows-2016-1'] +final def oses = ['linux':'ubuntu', 'windows':'Windows'] final def mavens = env.BRANCH_NAME == 'master' ? ['3.6.x', '3.2.x'] : ['3.6.x'] // all non-EOL versions and the first EA -final def jdks = [13, 12, 11, 8, 7] +final def jdks = [13, 12, 11, 8] final def options = ['-e', '-V', '-B', '-nsu', '-P', 'run-its'] final def goals = ['clean', 'install'] @@ -109,7 +109,7 @@ timeout(time: 12, unit: 'HOURS') { currentBuild.result = 'FAILURE' throw e } finally { - // jenkinsNotify() + jenkinsNotify() } } @@ -133,7 +133,7 @@ def buildProcess(String stageKey, String jdkName, String jdkTestName, String mvn } def properties = ["-Djacoco.skip=${!makeReports}", "\"-Dmaven.repo.local=${mvnLocalRepoDir}\""] - println "Setting JDK for testing ${jdkName}" + println "Setting JDK for testing ${jdkTestName}" def cmd = ['mvn'] + goals + options + properties stage("build ${stageKey}") { @@ -143,6 +143,7 @@ def buildProcess(String stageKey, String jdkName, String jdkTestName, String mvn "MAVEN_OPTS=${mavenOpts}", "PATH+MAVEN=${tool(mvnName)}/bin:${tool(jdkName)}/bin" ]) { + sh '$JAVA_HOME_IT/bin/java -version' sh 'echo JAVA_HOME=$JAVA_HOME, JAVA_HOME_IT=$JAVA_HOME_IT, PATH=$PATH' def script = cmd + ['\"-Djdk.home=$JAVA_HOME_IT\"'] def error = sh(returnStatus: true, script: script.join(' ')) @@ -154,6 +155,7 @@ def buildProcess(String stageKey, String jdkName, String jdkTestName, String mvn "MAVEN_OPTS=${mavenOpts}", "PATH+MAVEN=${tool(mvnName)}\\bin;${tool(jdkName)}\\bin" ]) { + bat '%JAVA_HOME_IT%\\bin\\java -version' bat 'echo JAVA_HOME=%JAVA_HOME%, JAVA_HOME_IT=%JAVA_HOME_IT%, PATH=%PATH%' def script = cmd + ['\"-Djdk.home=%JAVA_HOME_IT%\"'] def error = bat(returnStatus: true, script: script.join(' ')) @@ -205,6 +207,7 @@ static def sourcesPatternCsv() { '**/maven-surefire-report-plugin/src/main/java,' + '**/surefire-api/src/main/java,' + '**/surefire-booter/src/main/java,' + + '**/surefire-extensions-api/src/main/java,' + '**/surefire-grouper/src/main/java,' + '**/surefire-its/src/main/java,' + '**/surefire-logger-api/src/main/java,' + @@ -220,6 +223,7 @@ static def classPatternCsv() { '**/maven-surefire-report-plugin/target/classes,' + '**/surefire-api/target/classes,' + '**/surefire-booter/target/classes,' + + '**/surefire-extensions-api/target/classes,' + '**/surefire-grouper/target/classes,' + '**/surefire-its/target/classes,' + '**/surefire-logger-api/target/classes,' + diff --git a/maven-failsafe-plugin/pom.xml b/maven-failsafe-plugin/pom.xml index bf61284..2e85275 100644 --- a/maven-failsafe-plugin/pom.xml +++ b/maven-failsafe-plugin/pom.xml @@ -341,7 +341,7 @@ <configuration> <javaHome>${jdk.home}</javaHome> <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath> - <mavenOpts>-Dhttps.protocols=TLSv1,TLSv1.1,TLSv1.2</mavenOpts> + <mavenOpts>-Dhttps.protocols=TLSv1.2 -Djdk.tls.client.protocols=TLSv1.2</mavenOpts> </configuration> </plugin> </plugins> diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java index b4e303e..15b1dd0 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java @@ -207,8 +207,9 @@ public final class CommandReader public void stop() { - if ( state.compareAndSet( NEW, TERMINATED ) || state.compareAndSet( RUNNABLE, TERMINATED ) ) + if ( !isStopped() ) { + state.set( TERMINATED ); makeQueueFull(); listeners.clear(); commandThread.interrupt(); 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 ebc1b27..2144e5a 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 @@ -30,6 +30,8 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; import java.lang.reflect.InvocationTargetException; import java.security.AccessControlException; import java.security.AccessController; @@ -47,6 +49,7 @@ import static java.util.concurrent.TimeUnit.SECONDS; import static org.apache.maven.surefire.booter.SystemPropertyManager.setSystemProperties; import static org.apache.maven.surefire.util.ReflectionUtils.instantiateOneArg; import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory; +import static org.apache.maven.surefire.util.internal.StringUtils.NL; /** * The part of the booter that is unique to a forked vm. @@ -62,12 +65,13 @@ public final class ForkedBooter { private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30L; private static final long PING_TIMEOUT_IN_SECONDS = 30L; - private static final long ONE_SECOND_IN_MILLIS = 1000L; + private static final long ONE_SECOND_IN_MILLIS = 1_000L; private static final String LAST_DITCH_SHUTDOWN_THREAD = "surefire-forkedjvm-last-ditch-daemon-shutdown-thread-"; private static final String PING_THREAD = "surefire-forkedjvm-ping-"; private final CommandReader commandReader = CommandReader.getReader(); private final ForkedChannelEncoder eventChannel = new ForkedChannelEncoder( System.out ); + private final Semaphore exitBarrier = new Semaphore( 0 ); private volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS; private volatile PingScheduler pingScheduler; @@ -83,7 +87,6 @@ public final class ForkedBooter { BooterDeserializer booterDeserializer = new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) ); - // todo: print PID in debug console logger in version 2.21.2 pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid() ); setSystemProperties( new File( tmpDir, effectiveSystemPropertiesFileName ) ); @@ -91,6 +94,12 @@ public final class ForkedBooter DumpErrorSingleton.getSingleton() .init( providerConfiguration.getReporterConfiguration().getReportsDirectory(), dumpFileName ); + if ( isDebugging() ) + { + DumpErrorSingleton.getSingleton() + .dumpText( "Found Maven process ID " + booterDeserializer.getPluginPid() ); + } + startupConfiguration = booterDeserializer.getProviderConfiguration(); systemExitTimeoutInSeconds = providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS ); @@ -118,18 +127,21 @@ public final class ForkedBooter { runSuitesInProcess(); } - catch ( InvocationTargetException t ) + catch ( InvocationTargetException e ) { - Throwable e = t.getTargetException(); - DumpErrorSingleton.getSingleton().dumpException( e ); - eventChannel.consoleErrorLog( new LegacyPojoStackTraceWriter( "test subsystem", "no method", e ), false ); + Throwable t = e.getTargetException(); + DumpErrorSingleton.getSingleton().dumpException( t ); + eventChannel.consoleErrorLog( new LegacyPojoStackTraceWriter( "test subsystem", "no method", t ), false ); } catch ( Throwable t ) { DumpErrorSingleton.getSingleton().dumpException( t ); eventChannel.consoleErrorLog( new LegacyPojoStackTraceWriter( "test subsystem", "no method", t ), false ); } - acknowledgedExit(); + finally + { + acknowledgedExit(); + } } private Object createTestSet( TypeEncodedValue forkedTestSet, boolean readTestsFromCommandReader, ClassLoader cl ) @@ -202,7 +214,11 @@ public final class ForkedBooter && !pingMechanism.pingScheduler.isShutdown() ) { DumpErrorSingleton.getSingleton() - .dumpText( "Killing self fork JVM. Maven process died." ); + .dumpText( "Killing self fork JVM. Maven process died." + + NL + + "Thread dump before killing the process (" + getProcessName() + "):" + + NL + + generateThreadDump() ); kill(); } @@ -239,17 +255,33 @@ public final class ForkedBooter if ( shutdown.isKill() ) { DumpErrorSingleton.getSingleton() - .dumpText( "Killing self fork JVM. Received SHUTDOWN command from Maven shutdown hook." ); + .dumpText( "Killing self fork JVM. Received SHUTDOWN command from Maven shutdown hook." + + NL + + "Thread dump before killing the process (" + getProcessName() + "):" + + NL + + generateThreadDump() ); kill(); } else if ( shutdown.isExit() ) { cancelPingScheduler(); DumpErrorSingleton.getSingleton() - .dumpText( "Exiting self fork JVM. Received SHUTDOWN command from Maven shutdown hook." ); + .dumpText( "Exiting self fork JVM. Received SHUTDOWN command from Maven shutdown hook." + + NL + + "Thread dump before exiting the process (" + getProcessName() + "):" + + NL + + generateThreadDump() ); + exitBarrier.release(); exit1(); } - // else refers to shutdown=testset, but not used now, keeping reader open + else + { + // else refers to shutdown=testset, but not used now, keeping reader open + DumpErrorSingleton.getSingleton() + .dumpText( "Thread dump for process (" + getProcessName() + "):" + + NL + + generateThreadDump() ); + } } }; } @@ -267,7 +299,11 @@ public final class ForkedBooter if ( !hasPing ) { DumpErrorSingleton.getSingleton() - .dumpText( "Killing self fork JVM. PING timeout elapsed." ); + .dumpText( "Killing self fork JVM. PING timeout elapsed." + + NL + + "Thread dump before killing the process (" + getProcessName() + "):" + + NL + + generateThreadDump() ); kill(); } @@ -295,20 +331,19 @@ public final class ForkedBooter private void acknowledgedExit() { - final Semaphore barrier = new Semaphore( 0 ); commandReader.addByeAckListener( new CommandListener() { @Override public void update( Command command ) { - barrier.release(); + exitBarrier.release(); } } ); eventChannel.bye(); launchLastDitchDaemonShutdownThread( 0 ); long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS ); - acquireOnePermit( barrier, timeoutMillis ); + acquireOnePermit( exitBarrier, timeoutMillis ); cancelPingScheduler(); commandReader.stop(); System.exit( 0 ); @@ -342,14 +377,24 @@ public final class ForkedBooter @SuppressWarnings( "checkstyle:emptyblock" ) private void launchLastDitchDaemonShutdownThread( final int returnCode ) { - getJvmTerminator().schedule( new Runnable() - { - @Override - public void run() - { - kill( returnCode ); - } - }, systemExitTimeoutInSeconds, SECONDS + getJvmTerminator() + .schedule( new Runnable() + { + @Override + public void run() + { + DumpErrorSingleton.getSingleton() + .dumpText( "Thread dump for process (" + + getProcessName() + + ") after " + + systemExitTimeoutInSeconds + + " seconds shutdown timeout:" + + NL + + generateThreadDump() ); + + kill( returnCode ); + } + }, systemExitTimeoutInSeconds, SECONDS ); } @@ -385,9 +430,20 @@ public final class ForkedBooter * * @param args Commandline arguments */ - public static void main( String... args ) + public static void main( String[] args ) { ForkedBooter booter = new ForkedBooter(); + run( booter, args ); + } + + /** + * created for testing purposes. + * + * @param booter booter in JVM + * @param args arguments passed to JVM + */ + private static void run( ForkedBooter booter, String[] args ) + { try { booter.setupBooter( args[0], args[1], args[2], args.length > 3 ? args[3] : null ); @@ -472,4 +528,34 @@ public final class ForkedBooter return pingScheduler.isShutdown(); } } + + private static String generateThreadDump() + { + StringBuilder dump = new StringBuilder(); + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] threadInfos = threadMXBean.getThreadInfo( threadMXBean.getAllThreadIds(), 100 ); + for ( ThreadInfo threadInfo : threadInfos ) + { + dump.append( '"' ); + dump.append( threadInfo.getThreadName() ); + dump.append( "\" " ); + Thread.State state = threadInfo.getThreadState(); + dump.append( "\n java.lang.Thread.State: " ); + dump.append( state ); + StackTraceElement[] stackTraceElements = threadInfo.getStackTrace(); + for ( StackTraceElement stackTraceElement : stackTraceElements ) + { + dump.append( "\n at " ); + dump.append( stackTraceElement ); + } + dump.append( "\n\n" ); + } + return dump.toString(); + } + + private static String getProcessName() + { + return ManagementFactory.getRuntimeMXBean() + .getName(); + } } diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java new file mode 100644 index 0000000..e65beb1 --- /dev/null +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterMockTest.java @@ -0,0 +1,134 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.times; +import static org.powermock.api.mockito.PowerMockito.doCallRealMethod; +import static org.powermock.api.mockito.PowerMockito.doThrow; +import static org.powermock.api.mockito.PowerMockito.verifyNoMoreInteractions; +import static org.powermock.api.mockito.PowerMockito.verifyPrivate; +import static org.powermock.api.mockito.PowerMockito.when; +import static org.powermock.reflect.Whitebox.invokeMethod; + +/** + * PowerMock tests for {@link ForkedBooter}. + */ +@RunWith( PowerMockRunner.class ) +@PrepareForTest( { PpidChecker.class, ForkedBooter.class } ) +public class ForkedBooterMockTest +{ + @Mock + private PpidChecker pluginProcessChecker; + + @Mock + private ForkedBooter booter; + + @Test + public void shouldCheckNewPingMechanism() throws Exception + { + boolean canUse = invokeMethod( ForkedBooter.class, "canUseNewPingMechanism", (PpidChecker) null ); + assertThat( canUse ).isFalse(); + + when( pluginProcessChecker.canUse() ).thenReturn( false ); + canUse = invokeMethod( ForkedBooter.class, "canUseNewPingMechanism", pluginProcessChecker ); + assertThat( canUse ).isFalse(); + + when( pluginProcessChecker.canUse() ).thenReturn( true ); + canUse = invokeMethod( ForkedBooter.class, "canUseNewPingMechanism", pluginProcessChecker ); + assertThat( canUse ).isTrue(); + } + + @Test + public void testMain() throws Exception + { + PowerMockito.mockStatic( ForkedBooter.class ); + + ArgumentCaptor<String[]> capturedArgs = ArgumentCaptor.forClass( String[].class ); + ArgumentCaptor<ForkedBooter> capturedBooter = ArgumentCaptor.forClass( ForkedBooter.class ); + doCallRealMethod() + .when( ForkedBooter.class, "run", capturedBooter.capture(), capturedArgs.capture() ); + + String[] args = new String[]{ "/", "dump", "surefire.properties", "surefire-effective.properties" }; + invokeMethod( ForkedBooter.class, "run", booter, args ); + + assertThat( capturedBooter.getAllValues() ) + .hasSize( 1 ) + .contains( booter ); + + assertThat( capturedArgs.getAllValues() ) + .hasSize( 1 ); + assertThat( capturedArgs.getAllValues().get( 0 )[0] ) + .isEqualTo( "/" ); + assertThat( capturedArgs.getAllValues().get( 0 )[1] ) + .isEqualTo( "dump" ); + assertThat( capturedArgs.getAllValues().get( 0 )[2] ) + .isEqualTo( "surefire.properties" ); + assertThat( capturedArgs.getAllValues().get( 0 )[3] ) + .isEqualTo( "surefire-effective.properties" ); + + verifyPrivate( booter, times( 1 ) ) + .invoke( "setupBooter", same( args[0] ), same( args[1] ), same( args[2] ), same( args[3] ) ); + + verifyPrivate( booter, times( 1 ) ) + .invoke( "execute" ); + + verifyNoMoreInteractions( booter ); + } + + @Test + public void testMainWithError() throws Exception + { + PowerMockito.mockStatic( ForkedBooter.class ); + + doCallRealMethod() + .when( ForkedBooter.class, "run", any( ForkedBooter.class ), any( String[].class ) ); + + doThrow( new RuntimeException( "dummy exception" ) ) + .when( booter, "execute" ); + + String[] args = new String[]{ "/", "dump", "surefire.properties", "surefire-effective.properties" }; + invokeMethod( ForkedBooter.class, "run", booter, args ); + + verifyPrivate( booter, times( 1 ) ) + .invoke( "setupBooter", same( args[0] ), same( args[1] ), same( args[2] ), same( args[3] ) ); + + verifyPrivate( booter, times( 1 ) ) + .invoke( "execute" ); + + verifyPrivate( booter, times( 1 ) ) + .invoke( "cancelPingScheduler" ); + + verifyPrivate( booter, times( 1 ) ) + .invoke( "exit1" ); + + verifyNoMoreInteractions( booter ); + } +} diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java new file mode 100644 index 0000000..42feda6 --- /dev/null +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ForkedBooterTest.java @@ -0,0 +1,144 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.commons.io.FileUtils; +import org.junit.Test; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.ScheduledExecutorService; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.fest.assertions.Assertions.assertThat; +import static org.powermock.reflect.Whitebox.invokeMethod; + +/** + * Tests for {@link ForkedBooter}. + */ +public class ForkedBooterTest +{ + @Test + public void shouldGenerateThreadDump() throws Exception + { + Collection<String> threadNames = new ArrayList<>(); + ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); + for ( ThreadInfo threadInfo : threadMXBean.getThreadInfo( threadMXBean.getAllThreadIds(), 100 ) ) + { + threadNames.add( threadInfo.getThreadName() ); + } + + String dump = invokeMethod( ForkedBooter.class, "generateThreadDump" ); + + for ( String threadName : threadNames ) + { + assertThat( dump ) + .contains( "\"" + threadName + "\"" ); + } + + assertThat( dump ) + .contains( " java.lang.Thread.State: " ) + .contains( " at " ); + } + + @Test + public void shouldFindCurrentProcessName() throws Exception + { + String process = ManagementFactory.getRuntimeMXBean().getName(); + String expected = invokeMethod( ForkedBooter.class, "getProcessName" ); + assertThat( process ).isEqualTo( expected ); + } + + @Test + public void shouldNotBeDebugMode() throws Exception + { + boolean expected = invokeMethod( ForkedBooter.class, "isDebugging" ); + assertThat( expected ).isFalse(); + } + + @Test + public void shouldReadSurefireProperties() throws Exception + { + File target = new File( System.getProperty( "user.dir", "target" ) ); + File tmpDir = new File( target, "ForkedBooterTest.1" ); + assertThat( tmpDir.mkdirs() ) + .isTrue(); + + try + { + try ( InputStream is = invokeMethod( ForkedBooter.class, "createSurefirePropertiesIfFileExists", + tmpDir.getCanonicalPath(), "surefire.properties" ) ) + { + assertThat( is ) + .isNull(); + } + + File props = new File( tmpDir, "surefire.properties" ); + + assertThat( props.createNewFile() ) + .isTrue(); + + FileUtils.write( props, "key=value", UTF_8 ); + + try ( InputStream is2 = invokeMethod( ForkedBooter.class, "createSurefirePropertiesIfFileExists", + tmpDir.getCanonicalPath(), "surefire.properties" ) ) + { + assertThat( is2 ) + .isNotNull() + .isInstanceOf( FileInputStream.class ); + + byte[] propsContent = new byte[20]; + int length = is2.read( propsContent ); + + assertThat( new String( propsContent, 0, length ) ) + .isEqualTo( "key=value" ); + } + } + finally + { + FileUtils.deleteDirectory( tmpDir ); + } + } + + @Test + public void shouldCreateScheduler() throws Exception + { + ScheduledExecutorService scheduler = null; + try + { + scheduler = invokeMethod( ForkedBooter.class, "createPingScheduler" ); + assertThat( scheduler ) + .isNotNull(); + } + finally + { + if ( scheduler != null ) + { + scheduler.shutdownNow(); + } + } + } +} \ No newline at end of file diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java index ff731c4..1fc9de6 100644 --- a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java @@ -37,6 +37,8 @@ public class JUnit4SuiteTest extends TestCase TestSuite suite = new TestSuite(); suite.addTest( new JUnit4TestAdapter( PpidCheckerTest.class ) ); suite.addTest( new JUnit4TestAdapter( SystemUtilsTest.class ) ); + suite.addTest( new JUnit4TestAdapter( ForkedBooterTest.class ) ); + suite.addTest( new JUnit4TestAdapter( ForkedBooterMockTest.class ) ); suite.addTestSuite( ClasspathTest.class ); suite.addTestSuite( PropertiesWrapperTest.class ); return suite; diff --git a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire946KillMainProcessInReusableForkIT.java b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire946KillMainProcessInReusableForkIT.java index a5740c0..e1d09ba 100644 --- a/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire946KillMainProcessInReusableForkIT.java +++ b/surefire-its/src/test/java/org/apache/maven/surefire/its/jiras/Surefire946KillMainProcessInReusableForkIT.java @@ -19,48 +19,118 @@ package org.apache.maven.surefire.its.jiras; * under the License. */ +import com.googlecode.junittoolbox.ParallelParameterized; import org.apache.maven.surefire.its.fixture.SurefireJUnit4IntegrationTestCase; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; +import org.w3c.dom.Document; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathFactory; +import java.io.File; +import java.util.ArrayList; + +import static org.fest.assertions.Assertions.assertThat; + +@RunWith( ParallelParameterized.class ) public class Surefire946KillMainProcessInReusableForkIT extends SurefireJUnit4IntegrationTestCase { - // there are 10 test classes that each would wait 2 seconds. - private static final int TEST_SLEEP_TIME = 2000; + private static final Object LOCK_DEPENDENCY = new Object(); + private static final Object LOCK_PLUGIN = new Object(); - @BeforeClass - public static void installSelfdestructPlugin() - { - unpack( Surefire946KillMainProcessInReusableForkIT.class, "surefire-946-self-destruct-plugin", "plugin" ).executeInstall(); - } + // there are 10 test classes that each would wait 3.5 seconds. + private static final int TEST_SLEEP_TIME = 3_500; + + private String classifierOfDummyDependency; + + @Parameter + public String shutdownMavenMethod; - @Test( timeout = 30000 ) - public void testHalt() + @Parameter( 1 ) + public String shutdownSurefireMethod; + + @Parameters( name = "{0}-{1}") + public static Iterable<Object[]> data() { - doTest( "halt" ); + ArrayList<Object[]> args = new ArrayList<>(); + args.add( new Object[] { "halt", "exit" } ); + args.add( new Object[] { "halt", "kill" } ); + args.add( new Object[] { "exit", "exit" } ); + args.add( new Object[] { "exit", "kill" } ); + args.add( new Object[] { "interrupt", "exit" } ); + args.add( new Object[] { "interrupt", "kill" } ); + return args; } - @Test( timeout = 30000 ) - public void testExit() + @BeforeClass + public static void installSelfdestructPlugin() { - doTest( "exit" ); + synchronized ( LOCK_PLUGIN ) + { + unpack( Surefire946KillMainProcessInReusableForkIT.class, "surefire-946-self-destruct-plugin", "plugin" ) + .executeInstall(); + } } - @Test( timeout = 30000 ) - public void testInterrupt() + @Before + public void dummyDep() { - doTest( "interrupt" ); + synchronized ( LOCK_DEPENDENCY ) + { + classifierOfDummyDependency = shutdownMavenMethod + shutdownSurefireMethod; + unpack( Surefire946KillMainProcessInReusableForkIT.class, + "surefire-946-dummy-dependency", classifierOfDummyDependency ) + .sysProp( "distinct.classifier", classifierOfDummyDependency ) + .executeInstall(); + } } - private void doTest( String method ) + @Test( timeout = 60_000 ) + public void test() throws Exception { - unpack( "surefire-946-killMainProcessInReusableFork" ) - .sysProp( "selfdestruct.timeoutInMillis", "5000" ) - .sysProp( "selfdestruct.method", method ) - .sysProp( "testSleepTime", String.valueOf( TEST_SLEEP_TIME ) ) - .addGoal( "org.apache.maven.plugins.surefire:maven-selfdestruct-plugin:selfdestruct" ) - .setForkJvm() - .forkPerThread().threadCount( 1 ).reuseForks( true ).maven().withFailure().executeTest(); + unpack( "surefire-946-killMainProcessInReusableFork", + "-" + shutdownMavenMethod + "-" + shutdownSurefireMethod ) + .sysProp( "distinct.classifier", classifierOfDummyDependency ) + .sysProp( "surefire.shutdown", shutdownSurefireMethod ) + .sysProp( "selfdestruct.timeoutInMillis", "5000" ) + .sysProp( "selfdestruct.method", shutdownMavenMethod ) + .sysProp( "testSleepTime", String.valueOf( TEST_SLEEP_TIME ) ) + .addGoal( "org.apache.maven.plugins.surefire:maven-selfdestruct-plugin:selfdestruct" ) + .setForkJvm() + .forkPerThread().threadCount( 1 ).reuseForks( true ).maven().withFailure().executeTest(); + + + XPathFactory xpathFactory = XPathFactory.newInstance(); + XPath xpath = xpathFactory.newXPath(); + File settings = new File( System.getProperty( "maven.settings.file" ) ).getCanonicalFile(); + Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse( settings ); + String localRepository = xpath.evaluate( "/settings/localRepository", doc ); + assertThat( localRepository ) + .isNotNull() + .isNotEmpty(); + + File dep = new File( localRepository, + "org/apache/maven/plugins/surefire/surefire-946-dummy-dependency/0.1/" + + "surefire-946-dummy-dependency-0.1-" + classifierOfDummyDependency + ".jar" ); + + assertThat( dep ) + .exists(); + + boolean deleted; + int iterations = 0; + do + { + Thread.sleep( 1_000L ); + deleted = dep.delete(); + } + while ( !deleted && ++iterations < 10 ); + assertThat( deleted ) + .isTrue(); } } diff --git a/surefire-its/src/test/resources/surefire-946-dummy-dependency/pom.xml b/surefire-its/src/test/resources/surefire-946-dummy-dependency/pom.xml new file mode 100644 index 0000000..d2fdf86 --- /dev/null +++ b/surefire-its/src/test/resources/surefire-946-dummy-dependency/pom.xml @@ -0,0 +1,39 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>org.apache.maven.plugins.surefire</groupId> + <artifactId>surefire-946-dummy-dependency</artifactId> + <version>0.1</version> + + <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.compiler.source>1.7</maven.compiler.source> + <maven.compiler.target>1.7</maven.compiler.target> + </properties> + + <build> + <plugins> + <plugin> + <!-- + No test sources here. + Purpose to pre-install. + See the Surefire946KillMainProcessInReusableForkIT. + --> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire.version}</version> + <configuration> + <skipTests>true</skipTests> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <version>3.1.2</version> + <configuration> + <classifier>${distinct.classifier}</classifier> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic04Test.java b/surefire-its/src/test/resources/surefire-946-dummy-dependency/src/main/java/dummy/DummyClass.java similarity index 67% copy from surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic04Test.java copy to surefire-its/src/test/resources/surefire-946-dummy-dependency/src/main/java/dummy/DummyClass.java index 6d082cb..014cabb 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic04Test.java +++ b/surefire-its/src/test/resources/surefire-946-dummy-dependency/src/main/java/dummy/DummyClass.java @@ -1,5 +1,3 @@ -package junit44.environment; - /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file @@ -19,27 +17,16 @@ package junit44.environment; * under the License. */ -import org.junit.AfterClass; -import org.junit.Test; +package dummy; -public class Basic04Test +/** + * + */ +public class DummyClass { - - @Test - public void testNothing() + @Override + public String toString() { + return "JVM handles a file handler to 'surefire-946-dummy-dependency-0.1.jar'."; } - - @AfterClass - public static void waitSomeTimeAround() - { - try - { - Thread.sleep( Integer.getInteger( "testSleepTime", 2000 ) ); - } - catch ( InterruptedException ignored ) - { - } - } - -} +} \ No newline at end of file diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/pom.xml b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/pom.xml index 53bb0a3..2240fe4 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/pom.xml +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/pom.xml @@ -27,12 +27,19 @@ <name>Tests killing the main maven process when using reusable forks</name> <properties> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> </properties> <dependencies> <dependency> + <groupId>org.apache.maven.plugins.surefire</groupId> + <artifactId>surefire-946-dummy-dependency</artifactId> + <version>0.1</version> + <classifier>${distinct.classifier}</classifier> + </dependency> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic01Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic01Test.java index 8c52d1b..77971fd 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic01Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic01Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic01Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic02Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic02Test.java index 6ef33f9..1133efe 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic02Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic02Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic02Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic03Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic03Test.java index b1d7c71..15b1bd9 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic03Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic03Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic03Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic04Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic04Test.java index 6d082cb..4151336 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic04Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic04Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic04Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic05Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic05Test.java index 92f5f15..3f14261 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic05Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic05Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic05Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic06Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic06Test.java index 2a44568..1925665 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic06Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic06Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic06Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic07Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic07Test.java index 64f180e..77436bb 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic07Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic07Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic07Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic08Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic08Test.java index 5a4c382..ed62c32 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic08Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic08Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic08Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic09Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic09Test.java index 2461d11..87043e2 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic09Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic09Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic09Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic10Test.java b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic10Test.java index 1e57b13..d481da7 100644 --- a/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic10Test.java +++ b/surefire-its/src/test/resources/surefire-946-killMainProcessInReusableFork/src/test/java/junit44/environment/Basic10Test.java @@ -19,6 +19,7 @@ package junit44.environment; * under the License. */ +import dummy.DummyClass; import org.junit.AfterClass; import org.junit.Test; @@ -28,6 +29,7 @@ public class Basic10Test @Test public void testNothing() { + System.out.println( new DummyClass().toString() ); } @AfterClass diff --git a/surefire-its/src/test/resources/surefire-946-self-destruct-plugin/pom.xml b/surefire-its/src/test/resources/surefire-946-self-destruct-plugin/pom.xml index ad4ff00..5abb052 100644 --- a/surefire-its/src/test/resources/surefire-946-self-destruct-plugin/pom.xml +++ b/surefire-its/src/test/resources/surefire-946-self-destruct-plugin/pom.xml @@ -22,12 +22,6 @@ <artifactId>maven-plugin-api</artifactId> <version>2.0</version> </dependency> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <version>3.8.1</version> - <scope>test</scope> - </dependency> </dependencies> <build>
