Repository: maven-surefire Updated Branches: refs/heads/SUREFIRE-1302_2 8b6c3e0b0 -> f2d972424 (forced update)
[SUREFIRE-1302] Surefire does not wait long enough for the forked VM and assumes it to be dead Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/f2d97242 Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/f2d97242 Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/f2d97242 Branch: refs/heads/SUREFIRE-1302_2 Commit: f2d97242468ae10e9d4025db1f58386eb4a115d5 Parents: dd518d2 Author: Tibor17 <tibordig...@apache.org> Authored: Sat Jun 10 10:17:39 2017 +0200 Committer: Tibor17 <tibordig...@apache.org> Committed: Sun Jun 18 01:24:32 2017 +0200 ---------------------------------------------------------------------- maven-failsafe-plugin/pom.xml | 17 -- maven-surefire-common/pom.xml | 10 +- .../maven/plugin/surefire/SurefireHelper.java | 2 +- .../surefire/report/FileReporterUtils.java | 11 +- .../maven/plugin/surefire/util/ScannerUtil.java | 2 +- .../plugin/surefire/SurefireHelperTest.java | 2 +- .../booterclient/ForkConfigurationTest.java | 4 +- maven-surefire-plugin/pom.xml | 17 -- maven-surefire-report-plugin/pom.xml | 4 - pom.xml | 23 +- surefire-api/pom.xml | 10 +- .../maven/surefire/booter/CommandReader.java | 2 +- surefire-booter/pom.xml | 15 + .../maven/surefire/booter/ForkedBooter.java | 69 ++++- .../maven/surefire/booter/PpidChecker.java | 282 +++++++++++++++++++ .../maven/surefire/booter/ProcessInfo.java | 62 ++++ .../maven/surefire/booter/JUnit4SuiteTest.java | 3 +- .../maven/surefire/booter/PpidCheckerTest.java | 127 +++++++++ ...urefire1295AttributeJvmCrashesToTestsIT.java | 7 +- 19 files changed, 597 insertions(+), 72 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-failsafe-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/maven-failsafe-plugin/pom.xml b/maven-failsafe-plugin/pom.xml index f42e682..ec48929 100644 --- a/maven-failsafe-plugin/pom.xml +++ b/maven-failsafe-plugin/pom.xml @@ -45,27 +45,10 @@ <dependencies> <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-plugin-api</artifactId> - </dependency> - <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>maven-surefire-common</artifactId> </dependency> <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-api</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven.shared</groupId> - <artifactId>maven-shared-utils</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven.plugin-tools</groupId> - <artifactId>maven-plugin-annotations</artifactId> - <scope>compile</scope> - </dependency> - <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${project.version}</version> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-common/pom.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml index 4064bc2..670db71 100644 --- a/maven-surefire-common/pom.xml +++ b/maven-surefire-common/pom.xml @@ -87,12 +87,13 @@ <artifactId>maven-toolchain</artifactId> </dependency> <dependency> - <groupId>org.apache.commons</groupId> - <artifactId>commons-lang3</artifactId> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> </dependency> <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> + <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven.shared</groupId> @@ -192,6 +193,7 @@ <include>org.apache.maven.shared:maven-shared-utils</include> <include>org.apache.maven.shared:maven-common-artifact-filters</include> <include>commons-io:commons-io</include> + <include>commons-lang:commons-lang</include> </includes> </artifactSet> <relocations> @@ -203,6 +205,10 @@ <pattern>org.apache.commons.io</pattern> <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern> </relocation> + <relocation> + <pattern>org.apache.commons.lang</pattern> + <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern> + </relocation> </relocations> </configuration> </execution> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java index dd29cb4..32b06f9 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireHelper.java @@ -36,7 +36,7 @@ import java.util.Collection; import java.util.List; import static java.util.Collections.unmodifiableList; -import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS; +import static org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS; import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMPSTREAM_FILE_EXT; import static org.apache.maven.surefire.booter.DumpErrorSingleton.DUMP_FILE_EXT; import static org.apache.maven.surefire.cli.CommandLineOption.LOGGING_LEVEL_DEBUG; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java index 36bc269..60a75ad 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/report/FileReporterUtils.java @@ -19,6 +19,8 @@ package org.apache.maven.plugin.surefire.report; * under the License. */ +import static org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS; + /** * Utils class for file-based reporters * @@ -45,13 +47,6 @@ public final class FileReporterUtils private static String getOSSpecificIllegalChars() { - if ( System.getProperty( "os.name" ).toLowerCase().startsWith( "win" ) ) - { - return "\\/:*?\"<>|\0"; - } - else - { - return "/\0"; - } + return IS_OS_WINDOWS ? "\\/:*?\"<>|\0" : "/\0"; } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/ScannerUtil.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/ScannerUtil.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/ScannerUtil.java index 9989176..90dd49d 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/ScannerUtil.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/util/ScannerUtil.java @@ -19,7 +19,7 @@ package org.apache.maven.plugin.surefire.util; * under the License. */ -import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang.StringUtils; import javax.annotation.Nonnull; final class ScannerUtil http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java index c00f7f9..bf6b8c6 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/SurefireHelperTest.java @@ -26,7 +26,7 @@ import java.util.List; import static java.util.Collections.addAll; import static java.util.Collections.singleton; -import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS; +import static org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS; import static org.apache.maven.plugin.surefire.SurefireHelper.escapeToPlatformPath; import static org.fest.assertions.Assertions.assertThat; import static org.junit.Assume.assumeTrue; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java index 4f62670..89b7c27 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkConfigurationTest.java @@ -25,8 +25,8 @@ import java.util.Collections; import java.util.Properties; import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.RandomStringUtils; -import org.apache.commons.lang3.SystemUtils; +import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang.SystemUtils; import org.apache.maven.shared.utils.StringUtils; import org.apache.maven.shared.utils.cli.Commandline; import org.apache.maven.surefire.booter.Classpath; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/pom.xml b/maven-surefire-plugin/pom.xml index 62ec4a7..2a186e3 100644 --- a/maven-surefire-plugin/pom.xml +++ b/maven-surefire-plugin/pom.xml @@ -45,26 +45,9 @@ <dependencies> <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-plugin-api</artifactId> - </dependency> - <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>maven-surefire-common</artifactId> </dependency> - <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-api</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-toolchain</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven.plugin-tools</groupId> - <artifactId>maven-plugin-annotations</artifactId> - <scope>compile</scope> - </dependency> </dependencies> <build> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/maven-surefire-report-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-report-plugin/pom.xml b/maven-surefire-report-plugin/pom.xml index a4fe7e2..93a2e80 100644 --- a/maven-surefire-report-plugin/pom.xml +++ b/maven-surefire-report-plugin/pom.xml @@ -48,10 +48,6 @@ <dependencies> <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-logger-api</artifactId> - </dependency> - <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 962a5bf..bba1454 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,10 @@ <mavenVersion>2.2.1</mavenVersion> <!-- <shadedVersion>2.12.4</shadedVersion> commented out due to https://issues.apache.org/jira/browse/MRELEASE-799 --> <mavenPluginPluginVersion>3.3</mavenPluginPluginVersion> + <commonsLangVersion>2.4</commonsLangVersion> + <commonsLang3Version>3.1</commonsLang3Version> + <commonsIoVersion>2.2</commonsIoVersion> + <mavenSharedUtilsVersion>0.9</mavenSharedUtilsVersion> <maven.surefire.scm.devConnection>scm:git:https://git-wip-us.apache.org/repos/asf/maven-surefire.git</maven.surefire.scm.devConnection> <maven.site.path>surefire-archives/surefire-LATEST</maven.site.path> </properties> @@ -105,12 +109,17 @@ <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> - <version>3.1</version> + <version>${commonsLang3Version}</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>2.2</version> + <version>${commonsIoVersion}</version> + </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + <version>${commonsLangVersion}</version> </dependency> <dependency> <groupId>org.apache.maven.surefire</groupId> @@ -215,7 +224,13 @@ <dependency> <groupId>org.apache.maven.shared</groupId> <artifactId>maven-shared-utils</artifactId> - <version>0.9</version> + <version>${mavenSharedUtilsVersion}</version> + <exclusions> + <exclusion> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.apache.maven.shared</groupId> @@ -377,7 +392,7 @@ </plugin> <plugin> <artifactId>maven-shade-plugin</artifactId> - <version>1.5</version> + <version>3.0.0</version> </plugin> <plugin> <artifactId>maven-plugin-plugin</artifactId> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-api/pom.xml ---------------------------------------------------------------------- diff --git a/surefire-api/pom.xml b/surefire-api/pom.xml index 7e407d2..96c5be3 100644 --- a/surefire-api/pom.xml +++ b/surefire-api/pom.xml @@ -40,6 +40,11 @@ <groupId>org.apache.maven.shared</groupId> <artifactId>maven-shared-utils</artifactId> </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <scope>provided</scope> + </dependency> </dependencies> <build> @@ -74,7 +79,6 @@ <artifactSet> <includes> <include>org.apache.maven.shared:maven-shared-utils</include> - <include>commons-lang:commons-lang</include> </includes> </artifactSet> <relocations> @@ -82,10 +86,6 @@ <pattern>org.apache.maven.shared</pattern> <shadedPattern>org.apache.maven.surefire.shade.org.apache.maven.shared</shadedPattern> </relocation> - <relocation> - <pattern>org.apache.commons.lang</pattern> - <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern> - </relocation> </relocations> </configuration> </execution> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-api/src/main/java/org/apache/maven/surefire/booter/CommandReader.java ---------------------------------------------------------------------- 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 ed7d4fa..49179c3 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 @@ -493,7 +493,7 @@ public final class CommandReader { Runtime.getRuntime().halt( 1 ); } - // else is default: should not happen; otherwise you missed enum case + // else is default: other than Shutdown.DEFAULT should not happen; otherwise you missed enum case } } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-booter/pom.xml ---------------------------------------------------------------------- diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml index b79cceb..307f77d 100644 --- a/surefire-booter/pom.xml +++ b/surefire-booter/pom.xml @@ -36,6 +36,10 @@ <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-api</artifactId> </dependency> + <dependency> + <groupId>commons-lang</groupId> + <artifactId>commons-lang</artifactId> + </dependency> </dependencies> <build> @@ -54,6 +58,12 @@ <includes> <include>**/JUnit4SuiteTest.java</include> </includes> + <systemProperties> + <property> + <name>surefire.ppid</name> + <value>$PPID</value> + </property> + </systemProperties> </configuration> </plugin> <plugin> @@ -69,11 +79,16 @@ <minimizeJar>true</minimizeJar> <artifactSet> <includes> + <include>org.apache.maven.shared:maven-shared-utils</include> <include>commons-lang:commons-lang</include> </includes> </artifactSet> <relocations> <relocation> + <pattern>org.apache.maven.shared</pattern> + <shadedPattern>org.apache.maven.surefire.shade.org.apache.maven.shared</shadedPattern> + </relocation> + <relocation> <pattern>org.apache.commons.lang</pattern> <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern> </relocation> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/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 1e3863e..c19c21b 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 @@ -37,11 +37,14 @@ import java.lang.reflect.InvocationTargetException; import java.security.AccessControlException; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.Semaphore; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import static java.lang.Math.max; @@ -207,12 +210,52 @@ public final class ForkedBooter COMMAND_READER.addShutdownListener( createExitHandler() ); AtomicBoolean pingDone = new AtomicBoolean( true ); COMMAND_READER.addNoopListener( createPingHandler( pingDone ) ); - Runnable pingJob = createPingJob( pingDone ); ScheduledExecutorService pingScheduler = createPingScheduler(); - pingScheduler.scheduleAtFixedRate( pingJob, 0, PING_TIMEOUT_IN_SECONDS, SECONDS ); + Future<PpidChecker> checker = pingScheduler.submit( createProcessCheckerJob() ); + pingScheduler.scheduleWithFixedDelay( processCheckerJob( checker ), 0L, 1L, SECONDS ); + pingScheduler.scheduleAtFixedRate( createPingJob( pingDone, checker ), 0L, PING_TIMEOUT_IN_SECONDS, SECONDS ); return pingScheduler; } + private static Runnable processCheckerJob( final Future<PpidChecker> processChecker ) + { + return new Runnable() + { + @Override + public void run() + { + if ( processChecker.isDone() && !processChecker.isCancelled() ) + { + try + { + PpidChecker checker = processChecker.get(); + if ( checker.canUse() && !checker.isParentProcessAlive() ) + { + kill(); + } + } + catch ( Exception e ) + { + // nothing to do + } + } + } + }; + } + + private static Callable<PpidChecker> createProcessCheckerJob() + { + return new Callable<PpidChecker>() + { + @Override + public PpidChecker call() throws Exception + { + TimeUnit.MILLISECONDS.sleep( ONE_SECOND_IN_MILLIS ); + return new PpidChecker(); + } + }; + } + private static CommandListener createPingHandler( final AtomicBoolean pingDone ) { return new CommandListener() @@ -246,13 +289,17 @@ public final class ForkedBooter }; } - private static Runnable createPingJob( final AtomicBoolean pingDone ) + private static Runnable createPingJob( final AtomicBoolean pingDone, final Future<PpidChecker> processChecker ) { return new Runnable() { @Override public void run() { + if ( canUseNewPingMechanism( processChecker ) ) + { + return; + } boolean hasPing = pingDone.getAndSet( false ); if ( !hasPing ) { @@ -262,6 +309,18 @@ public final class ForkedBooter }; } + private static boolean canUseNewPingMechanism( Future<PpidChecker> processChecker ) + { + try + { + return ( !processChecker.isDone() || processChecker.get().canUse() ) && !processChecker.isCancelled(); + } + catch ( Exception e ) + { + return false; + } + } + private static void encodeAndWriteToOutput( String string, PrintStream out ) { byte[] encodeBytes = encodeStringForForkCommunication( string ); @@ -357,8 +416,8 @@ public final class ForkedBooter { ThreadFactory threadFactory = newDaemonThreadFactory( "ping-" + PING_TIMEOUT_IN_SECONDS + "s" ); ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory ); - executor.setMaximumPoolSize( 1 ); - executor.prestartCoreThread(); + executor.setKeepAliveTime( 3L, SECONDS ); + executor.setMaximumPoolSize( 3 ); return executor; } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java new file mode 100644 index 0000000..24c29bf --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java @@ -0,0 +1,282 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.maven.shared.utils.io.IOUtil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.management.ManagementFactory; +import java.util.Locale; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static org.apache.commons.lang.SystemUtils.IS_OS_UNIX; +import static org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS; +import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO; + +/** + * Recognizes PPID. Determines lifetime of parent process. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.20.1 + */ +final class PpidChecker +{ + private static final long MINUTES_TO_SECONDS = 60L; + + private static final long HOURS_TO_SECONDS = 60L * MINUTES_TO_SECONDS; + + private static final long DAYS_TO_SECONDS = 24L * HOURS_TO_SECONDS; + + private static final String WMIC_PPID = "ParentProcessId"; + + private static final String WMIC_CREATION_DATE = "CreationDate"; + + private static final String WINDOWS_CMD = + "wmic process where (ProcessId=%s) get " + WMIC_CREATION_DATE + ", " + WMIC_PPID; + + private static final String UNIX_CMD = "ps -o etime -p %s"; + + private static final Pattern UNIX_CMD_OUT_PATTERN = + Pattern.compile( "^((([\\d]+)-)?([\\d]{2}))?[:]?([\\d]{2}):([\\d]{2})$" ); + + private static final Pattern NUMBER_PATTERN = Pattern.compile( "^[\\d]+$" ); + + private final ProcessInfo parentProcessInfo; + + PpidChecker() + { + ProcessInfo parentProcess = INVALID_PROCESS_INFO; + if ( IS_OS_WINDOWS ) + { + String pid = pid(); + if ( pid != null ) + { + ProcessInfo currentProcessInfo = windows( pid ); + String ppid = currentProcessInfo.getPPID(); + parentProcess = currentProcessInfo.isValid() ? windows( ppid ) : INVALID_PROCESS_INFO; + } + } + else if ( IS_OS_UNIX ) + { + parentProcess = unix( System.getProperty( "surefire.ppid" ) ); + } + parentProcessInfo = parentProcess.isValid() ? parentProcess : INVALID_PROCESS_INFO; + } + + boolean canUse() + { + return parentProcessInfo.isValid(); + } + + boolean isParentProcessAlive() + { + if ( !canUse() ) + { + throw new IllegalStateException(); + } + + if ( IS_OS_WINDOWS ) + { + ProcessInfo pp = windows( parentProcessInfo.getPID() ); + // let's compare creation time, should be same unless killed or PPID is reused by OS into another process + return pp.isValid() && parentProcessInfo.getTime().equals( pp.getTime() ); + } + else if ( IS_OS_UNIX ) + { + ProcessInfo pp = unix( parentProcessInfo.getPID() ); + // let's compare elapsed time, should be greater or equal if parent process is the same and still alive + return pp.isValid() && (Long) pp.getTime() >= (Long) parentProcessInfo.getTime(); + } + + throw new IllegalStateException(); + } + + static long fromDays( Matcher matcher ) + { + String s = matcher.group( 3 ); + return s == null ? 0L : DAYS_TO_SECONDS * Byte.parseByte( s ); + } + + static long fromHours( Matcher matcher ) + { + String s = matcher.group( 4 ); + return s == null ? 0L : HOURS_TO_SECONDS * Byte.parseByte( s ); + } + + private static long fromMinutes( Matcher matcher ) + { + String s = matcher.group( 5 ); + return s == null ? 0L : MINUTES_TO_SECONDS * Byte.parseByte( s ); + } + + private static long fromSeconds( Matcher matcher ) + { + String s = matcher.group( 6 ); + return s == null ? 0L : Byte.parseByte( s ); + } + + // http://manpages.ubuntu.com/manpages/precise/en/man1/ps.1.html + // https://www.freebsd.org/cgi/man.cgi?query=ps&manpath=SuSE+Linux/i386+11.3 + + // http://manpages.ubuntu.com/manpages/xenial/man1/ps.1.html + // etime ELAPSED elapsed time since the process was started, in + // the form [[DD-]hh:]mm:ss. + + static ProcessInfo unix( String pid ) + { + String[] cmd = { "/bin/sh", "-c", String.format( Locale.ROOT, UNIX_CMD, pid ) }; + ProcessBuilder probuilder = new ProcessBuilder( cmd ); + Process p = null; + BufferedReader reader = null; + ProcessInfo processInfo = INVALID_PROCESS_INFO; + try + { + p = probuilder.start(); + reader = new BufferedReader( new InputStreamReader( p.getInputStream() ) ); + for ( String line = reader.readLine(); line != null; line = reader.readLine() ) + { + line = line.trim(); + if ( !line.isEmpty() ) + { + Matcher matcher = UNIX_CMD_OUT_PATTERN.matcher( line ); + if ( matcher.matches() ) + { + long pidUptime = fromDays( matcher ) + + fromHours( matcher ) + + fromMinutes( matcher ) + + fromSeconds( matcher ); + processInfo = new ProcessInfo( pid, pidUptime, null ); + } + } + } + p.waitFor(); + return processInfo; + } + catch ( IOException e ) + { + return processInfo; + } + catch ( InterruptedException e ) + { + return processInfo; + } + finally + { + IOUtil.close( reader ); + if ( p != null ) + { + p.destroy(); + } + } + } + + static String pid() + { + String processName = ManagementFactory.getRuntimeMXBean().getName(); + if ( processName != null && processName.contains( "@" ) ) + { + String pid = processName.substring( 0, processName.indexOf( '@' ) ).trim(); + if ( NUMBER_PATTERN.matcher( pid ).matches() ) + { + return pid; + } + } + return null; + } + + static ProcessInfo windows( String pid ) + { + Process p = null; + BufferedReader reader = null; + ProcessInfo processInfo = INVALID_PROCESS_INFO; + try + { + String[] cmd = { "CMD", "/A/C", String.format( Locale.ROOT, WINDOWS_CMD, pid ) }; + ProcessBuilder probuilder = new ProcessBuilder( cmd ); + p = probuilder.start(); + reader = new BufferedReader( new InputStreamReader( p.getInputStream() ) ); + boolean hasHeader = false; + boolean isStartTimestampFirst = false; + for ( String line = reader.readLine(); line != null; line = reader.readLine() ) + { + line = line.trim(); + + if ( line.isEmpty() ) + { + continue; + } + + if ( hasHeader ) + { + StringTokenizer args = new StringTokenizer( line ); + if ( args.countTokens() == 2 ) + { + if ( isStartTimestampFirst ) + { + String startTimestamp = args.nextToken(); + String ppid = args.nextToken(); + processInfo = new ProcessInfo( pid, startTimestamp, ppid ); + } + else + { + String ppid = args.nextToken(); + String startTimestamp = args.nextToken(); + processInfo = new ProcessInfo( pid, startTimestamp, ppid ); + } + } + } + else + { + StringTokenizer args = new StringTokenizer( line ); + if ( args.countTokens() == 2 ) + { + String arg0 = args.nextToken(); + String arg1 = args.nextToken(); + isStartTimestampFirst = WMIC_CREATION_DATE.equals( arg0 ); + hasHeader = isStartTimestampFirst || WMIC_PPID.equals( arg0 ); + hasHeader &= WMIC_CREATION_DATE.equals( arg1 ) || WMIC_PPID.equals( arg1 ); + } + } + } + p.waitFor(); + return processInfo; + } + catch ( IOException e ) + { + return processInfo; + } + catch ( InterruptedException e ) + { + return processInfo; + } + finally + { + IOUtil.close( reader ); + if ( p != null ) + { + p.destroy(); + } + } + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java new file mode 100644 index 0000000..addee1d --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java @@ -0,0 +1,62 @@ +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. + */ + +/** + * PID, PPID, elapsed time (Unix) or start time (Windows). + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.20.1 + */ +final class ProcessInfo +{ + static final ProcessInfo INVALID_PROCESS_INFO = new ProcessInfo( null, null, null ); + + private final String pid; + private final Object time; + private final String ppid; + + ProcessInfo( String pid, Object time, String ppid ) + { + this.pid = pid; + this.time = time; + this.ppid = ppid; + } + + boolean isValid() + { + return pid != null && time != null; + } + + String getPID() + { + return pid; + } + + Object getTime() + { + return time; + } + + String getPPID() + { + return ppid; + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/JUnit4SuiteTest.java index 2bdcf21..b08423f 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 @@ -34,7 +34,8 @@ import org.junit.runners.Suite; ClasspathTest.class, CommandReaderTest.class, PropertiesWrapperTest.class, - SurefireReflectorTest.class + SurefireReflectorTest.class, + PpidCheckerTest.class } ) @RunWith( Suite.class ) public class JUnit4SuiteTest http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java new file mode 100644 index 0000000..ee7ecee --- /dev/null +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java @@ -0,0 +1,127 @@ +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 java.util.concurrent.TimeUnit; + +import static org.apache.commons.lang.SystemUtils.IS_OS_UNIX; +import static org.apache.commons.lang.SystemUtils.IS_OS_WINDOWS; +import static org.fest.assertions.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; + +/** + * Testing {@link PpidChecker} on a platform. + * + * @author <a href="mailto:tibordig...@apache.org">Tibor Digana (tibor17)</a> + * @since 2.20.1 + */ +public class PpidCheckerTest +{ + @Test + public void shouldHavePid() + { + String pid = PpidChecker.pid(); + + assertThat( pid ) + .isNotNull(); + + assertThat( pid ) + .matches( "^[\\d]+$" ); + } + + @Test + public void shouldHavePpidAsWindows() + { + assumeTrue( IS_OS_WINDOWS ); + + ProcessInfo processInfo = PpidChecker.windows( PpidChecker.pid() ); + + assertThat( processInfo ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .matches( "^[\\d]+$" ); + + assertThat( processInfo.getTime() ) + .isNotNull(); + + processInfo = PpidChecker.windows( processInfo.getPID() ); + + assertThat( processInfo.getPID() ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .matches( "^[\\d]+$" ); + + assertThat( processInfo.getTime() ) + .isNotNull(); + } + + @Test + public void shouldHavePpidAsUnix() + { + assumeTrue( IS_OS_UNIX ); + + ProcessInfo processInfo = PpidChecker.unix( PpidChecker.pid() ); + + assertThat( processInfo ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .matches( "^[\\d]+$" ); + + assertThat( processInfo.getTime() ) + .isNotNull(); + + processInfo = PpidChecker.unix( processInfo.getPID() ); + + assertThat( processInfo.getPID() ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .matches( "^[\\d]+$" ); + + assertThat( processInfo.getTime() ) + .isNotNull(); + } + + @Test + public void shouldFindAliveParentProcess() + throws InterruptedException + { + PpidChecker checker = new PpidChecker(); + + assertThat( checker.canUse() ) + .isTrue(); + + TimeUnit.MILLISECONDS.sleep( 100L ); + + assertThat( checker.isParentProcessAlive() ) + .isTrue(); + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/f2d97242/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java ---------------------------------------------------------------------- diff --git a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java index 1fa88f6..62c37f0 100644 --- a/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java +++ b/surefire-integration-tests/src/test/java/org/apache/maven/surefire/its/jiras/Surefire1295AttributeJvmCrashesToTestsIT.java @@ -27,8 +27,10 @@ import org.junit.Before; import org.junit.Test; import java.util.Iterator; -import java.util.Locale; +import static org.apache.commons.lang.SystemUtils.IS_OS_LINUX; +import static org.apache.commons.lang.SystemUtils.IS_OS_MAC; +import static org.apache.commons.lang.SystemUtils.IS_OS_MAC_OSX; import static org.fest.assertions.Assertions.assertThat; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -46,8 +48,7 @@ public class Surefire1295AttributeJvmCrashesToTestsIT @Before public void skipWindows() { - String os = System.getProperty( "os.name" ).toLowerCase( Locale.ROOT ); - assumeTrue( os.equals( "mac os x" ) || os.equals( "linux" ) /*|| os.contains( "windows" )*/ ); + assumeTrue( IS_OS_LINUX || IS_OS_MAC || IS_OS_MAC_OSX ); } @Test