Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 9258D200CD8 for ; Wed, 19 Jul 2017 02:54:20 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 90D6C167EFE; Wed, 19 Jul 2017 00:54:20 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 3F67D167EFD for ; Wed, 19 Jul 2017 02:54:18 +0200 (CEST) Received: (qmail 54427 invoked by uid 500); 19 Jul 2017 00:54:17 -0000 Mailing-List: contact commits-help@maven.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@maven.apache.org Delivered-To: mailing list commits@maven.apache.org Received: (qmail 54418 invoked by uid 99); 19 Jul 2017 00:54:17 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 19 Jul 2017 00:54:17 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 3BD45E0A38; Wed, 19 Jul 2017 00:54:17 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: tibordigana@apache.org To: commits@maven.apache.org Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: maven-surefire git commit: SUREFIRE-1302_4 [Forced Update!] Date: Wed, 19 Jul 2017 00:54:17 +0000 (UTC) archived-at: Wed, 19 Jul 2017 00:54:20 -0000 Repository: maven-surefire Updated Branches: refs/heads/SUREFIRE-1302_4 ffe12542d -> 89e1e25d0 (forced update) SUREFIRE-1302_4 Project: http://git-wip-us.apache.org/repos/asf/maven-surefire/repo Commit: http://git-wip-us.apache.org/repos/asf/maven-surefire/commit/89e1e25d Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/89e1e25d Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/89e1e25d Branch: refs/heads/SUREFIRE-1302_4 Commit: 89e1e25d0f0e6cbad38e6d5e2256697e1d491127 Parents: 4de017b Author: Tibor17 Authored: Wed Jul 19 01:21:41 2017 +0200 Committer: Tibor17 Committed: Wed Jul 19 02:53:37 2017 +0200 ---------------------------------------------------------------------- maven-failsafe-plugin/pom.xml | 17 -- maven-surefire-common/pom.xml | 13 +- .../plugin/surefire/SurefireProperties.java | 8 + .../surefire/booterclient/BooterSerializer.java | 6 +- .../surefire/booterclient/ForkStarter.java | 70 ++++- .../surefire/report/FileReporterUtils.java | 11 +- ...erDeserializerProviderConfigurationTest.java | 3 +- ...terDeserializerStartupConfigurationTest.java | 3 +- .../surefire/booterclient/ForkStarterTest.java | 83 ++++++ .../apache/maven/surefire/JUnit4SuiteTest.java | 4 +- maven-surefire-plugin/pom.xml | 17 -- .../src/site/apt/examples/shutdown.apt.vm | 26 +- maven-surefire-report-plugin/pom.xml | 4 - pom.xml | 17 +- surefire-api/pom.xml | 10 +- .../maven/surefire/booter/CommandReader.java | 4 +- .../maven/surefire/util/ReflectionUtils.java | 49 +++- .../java/org/apache/maven/JUnit4SuiteTest.java | 4 +- .../surefire/util/ReflectionUtilsTest.java | 110 +++++++ surefire-booter/pom.xml | 25 +- .../maven/surefire/booter/BooterConstants.java | 1 + .../surefire/booter/BooterDeserializer.java | 8 + .../maven/surefire/booter/ForkedBooter.java | 74 +++-- .../maven/surefire/booter/PpidChecker.java | 285 +++++++++++++++++++ .../maven/surefire/booter/ProcessInfo.java | 110 +++++++ .../surefire/booter/PropertiesWrapper.java | 6 + .../maven/surefire/booter/JUnit4SuiteTest.java | 3 +- .../maven/surefire/booter/PpidCheckerTest.java | 131 +++++++++ ...urefire1295AttributeJvmCrashesToTestsIT.java | 6 +- 29 files changed, 1012 insertions(+), 96 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 @@ - org.apache.maven - maven-plugin-api - - org.apache.maven.surefire maven-surefire-common - org.apache.maven.surefire - surefire-api - - - org.apache.maven.shared - maven-shared-utils - - - org.apache.maven.plugin-tools - maven-plugin-annotations - compile - - org.apache.maven.plugins maven-surefire-plugin ${project.version} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/maven-surefire-common/pom.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml index 4064bc2..e81a8fd 100644 --- a/maven-surefire-common/pom.xml +++ b/maven-surefire-common/pom.xml @@ -36,6 +36,11 @@ 2.2.1 + + + ${java.home}/../bin/java + + org.apache.maven @@ -44,7 +49,6 @@ org.apache.maven.plugin-tools maven-plugin-annotations - compile org.apache.maven.surefire @@ -93,6 +97,7 @@ com.google.code.findbugs jsr305 + provided org.apache.maven.shared @@ -160,6 +165,7 @@ maven-surefire-plugin + ${test.jdk} true **/JUnit4SuiteTest.java @@ -192,6 +198,7 @@ org.apache.maven.shared:maven-shared-utils org.apache.maven.shared:maven-common-artifact-filters commons-io:commons-io + org.apache.commons:commons-lang3 @@ -203,6 +210,10 @@ org.apache.commons.io org.apache.maven.surefire.shade.org.apache.commons.io + + org.apache.commons.lang3 + org.apache.maven.surefire.shade.org.apache.commons.lang3 + http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java index 4b13898..3783376 100644 --- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java +++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/SurefireProperties.java @@ -196,6 +196,14 @@ public class SurefireProperties } } + public void setProperty( String key, Long value ) + { + if ( value != null ) + { + setProperty( key, value.toString() ); + } + } + public void addList( List items, String propertyPrefix ) { if ( items != null && !items.isEmpty() ) http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 0299525..591e89c 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 @@ -70,12 +70,14 @@ class BooterSerializer * Does not modify sourceProperties */ File serialize( KeyValueSource sourceProperties, ProviderConfiguration booterConfiguration, - StartupConfiguration providerConfiguration, Object testSet, boolean readTestsFromInStream ) + StartupConfiguration providerConfiguration, Object testSet, boolean readTestsFromInStream, + Long pid ) throws IOException { - SurefireProperties properties = new SurefireProperties( sourceProperties ); + properties.setProperty( PLUGIN_PID, pid ); + ClasspathConfiguration cp = providerConfiguration.getClasspathConfiguration(); properties.setClasspath( ClasspathConfiguration.CLASSPATH, cp.getTestClasspath() ); properties.setClasspath( ClasspathConfiguration.SUREFIRE_CLASSPATH, cp.getProviderClasspath() ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 a2a5095..acb5a72 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 @@ -54,6 +54,7 @@ import org.apache.maven.surefire.util.DefaultScanResult; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.lang.management.ManagementFactory; import java.util.ArrayList; import java.util.Collection; import java.util.Map; @@ -78,6 +79,9 @@ import static java.util.Collections.addAll; import static java.util.concurrent.Executors.newScheduledThreadPool; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; +import static org.apache.commons.lang3.JavaVersion.JAVA_9; +import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT; +import static org.apache.commons.lang3.SystemUtils.IS_OS_LINUX; import static org.apache.maven.plugin.surefire.AbstractSurefireMojo.createCopyAndReplaceForkNumPlaceholder; import static org.apache.maven.plugin.surefire.SurefireHelper.DUMP_FILE_PREFIX; import static org.apache.maven.plugin.surefire.booterclient.ForkNumberBucket.drawNumber; @@ -92,6 +96,8 @@ import static org.apache.maven.surefire.booter.SystemPropertyManager.writeProper import static org.apache.maven.surefire.suite.RunResult.SUCCESS; import static org.apache.maven.surefire.suite.RunResult.failure; import static org.apache.maven.surefire.suite.RunResult.timeout; +import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodChain; +import static org.apache.maven.surefire.util.ReflectionUtils.tryLoadClass; import static org.apache.maven.surefire.util.internal.ConcurrencyUtils.countDownToZero; import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThread; import static org.apache.maven.surefire.util.internal.DaemonThreadFactory.newDaemonThreadFactory; @@ -114,6 +120,8 @@ import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1; */ public class ForkStarter { + private static final Long PID = pid(); + private static final String EXECUTION_EXCEPTION = "ExecutionException"; private static final long PING_IN_SECONDS = 10; @@ -550,7 +558,8 @@ public class ForkStarter BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration ); surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration, - startupConfiguration, testSet, readTestsFromInStream ); + startupConfiguration, testSet, + readTestsFromInStream, PID ); if ( effectiveSystemProperties != null ) { @@ -806,4 +815,63 @@ public class ForkStarter } }, 0, TIMEOUT_CHECK_PERIOD_MILLIS, MILLISECONDS ); } + + static Long pid() + { + if ( JAVA_RECENT.atLeast( JAVA_9 ) ) + { + Long pid = pidOnJava9(); + if ( pid != null ) + { + return pid; + } + } + + if ( IS_OS_LINUX ) + { + try + { + return pidOnLinux(); + } + catch ( Exception e ) + { + // examine PID via JMX + } + } + + return pidOnJMX(); + } + + static Long pidOnJMX() + { + String processName = ManagementFactory.getRuntimeMXBean().getName(); + if ( processName.contains( "@" ) ) + { + String pid = processName.substring( 0, processName.indexOf( '@' ) ).trim(); + try + { + return Long.parseLong( pid ); + } + catch ( NumberFormatException e ) + { + return null; + } + } + + return null; + } + + static Long pidOnLinux() throws Exception + { + String pid = new File( "/proc/self" ).getCanonicalFile().getName(); + return Long.parseLong( pid ); + } + + static Long pidOnJava9() + { + ClassLoader classLoader = currentThread().getContextClassLoader(); + Class processHandle = tryLoadClass( classLoader, "java.lang.ProcessHandle" ); + String[] methodChain = { "current", "pid" }; + return (Long) invokeMethodChain( processHandle, methodChain, null ); + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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..fd33d8e 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.lang3.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/89e1e25d/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 6759367..5d970d8 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 @@ -220,8 +220,9 @@ public class BooterDeserializerProviderConfigurationTest test = "aTest"; } final File propsTest = booterSerializer.serialize( props, booterConfiguration, testProviderConfiguration, test, - readTestsFromInStream ); + readTestsFromInStream, 51L ); BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) ); + assertEquals( 51L, (Object) booterDeserializer.getPluginPid() ); return booterDeserializer.deserialize(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 1ca20d2..42fb525 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 @@ -124,8 +124,9 @@ public class BooterDeserializerStartupConfigurationTest BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration ); String aTest = "aTest"; final File propsTest = - booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest, false ); + booterSerializer.serialize( props, getProviderConfiguration(), startupConfiguration, aTest, false, 51L ); BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) ); + assertEquals( 51L, (Object) booterDeserializer.getPluginPid() ); return booterDeserializer.getProviderConfiguration(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java new file mode 100644 index 0000000..6cf9990 --- /dev/null +++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/booterclient/ForkStarterTest.java @@ -0,0 +1,83 @@ +package org.apache.maven.plugin.surefire.booterclient; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.junit.Test; + +import java.lang.management.ManagementFactory; + +import static org.apache.commons.lang3.JavaVersion.JAVA_9; +import static org.apache.commons.lang3.JavaVersion.JAVA_RECENT; +import static org.apache.commons.lang3.SystemUtils.IS_OS_LINUX; +import static org.fest.assertions.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; + +/** + * Unit test for {@link ForkStarter}. + * + * @author Tibor Digana (tibor17) + * @since 2.20.1 + */ +public class ForkStarterTest +{ + @Test + public void shouldBePidOnJigsaw() + { + assumeTrue( JAVA_RECENT.atLeast( JAVA_9 ) ); + + Long actualPid = ForkStarter.pidOnJava9(); + String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim(); + + assertThat( actualPid + "" ) + .isEqualTo( expectedPid ); + } + + @Test + public void shouldBePidOnLinux() throws Exception + { + assumeTrue( IS_OS_LINUX ); + + Long actualPid = ForkStarter.pidOnLinux(); + String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim(); + + assertThat( actualPid + "" ) + .isEqualTo( expectedPid ); + } + + @Test + public void shouldBePidOnJMX() + { + Long actualPid = ForkStarter.pidOnJMX(); + String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim(); + + assertThat( actualPid + "" ) + .isEqualTo( expectedPid ); + } + + @Test + public void shouldBePid() + { + Long actualPid = ForkStarter.pid(); + String expectedPid = ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim(); + + assertThat( actualPid + "" ) + .isEqualTo( expectedPid ); + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java ---------------------------------------------------------------------- diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java index f7cec19..a12f875 100644 --- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java +++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java @@ -27,6 +27,7 @@ import org.apache.maven.plugin.surefire.SurefirePropertiesTest; import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerProviderConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.BooterDeserializerStartupConfigurationTest; import org.apache.maven.plugin.surefire.booterclient.ForkConfigurationTest; +import org.apache.maven.plugin.surefire.booterclient.ForkStarterTest; import org.apache.maven.plugin.surefire.booterclient.ForkingRunListenerTest; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestLessInputStreamBuilderTest; import org.apache.maven.plugin.surefire.booterclient.lazytestprovider.TestProvidingInputStreamTest; @@ -72,7 +73,8 @@ import org.junit.runners.Suite; TestLessInputStreamBuilderTest.class, SPITest.class, SurefireReflectorTest.class, - SurefireHelperTest.class + SurefireHelperTest.class, + ForkStarterTest.class } ) @RunWith( Suite.class ) public class JUnit4SuiteTest http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 @@ - org.apache.maven - maven-plugin-api - - org.apache.maven.surefire maven-surefire-common - - org.apache.maven.surefire - surefire-api - - - org.apache.maven - maven-toolchain - - - org.apache.maven.plugin-tools - maven-plugin-annotations - compile - http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 index a546853..7126247 100644 --- a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm +++ b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm @@ -39,8 +39,32 @@ Shutdown of Forked JVM * Pinging forked JVM + << Since ${thisPlugin} Plugin 2.20.1 ping is platform dependent and fallbacks to old mechanism if PID of Maven + process or platform is not recognized, native commands fail in Java. >> + + Simply the mechanism checks the <<< Maven PID >>> is still alive and it is not reused by OS in another application. + If Maven process has died, the forked JVM is killed. + + << Implementation: >> The <<< Maven PID >>> is determined by: + + * resolving the link <<< /proc/self >>> on Linux, or + + * the JMX call <<< ManagementFactory.getRuntimeMXBean().getName() >>>, or + + * Java 9 call <<< ProcessHandle.current().pid() >>>. + + [] + + On Unix like systems the process' uptime is determined by native command <<< (/usr)/bin/ps -o etime= -p [PID] >>>. + + On Windows the start time is determined using <<< wmic process where (ProcessId=[PID]) get CreationDate >>> + in the forked JVM. + + + << Since ${thisPlugin} Plugin 2.19 the old mechanism is significantly slower: >> + The master process sends NOOP command to a forked JVM every 10 seconds. - Forked JVM is waiting for the command every 20 seconds. + Forked JVM is waiting for the command every 30 seconds. If the master process is killed (received SIGKILL signal) or shutdown (pressed CTRL+C, received SIGTERM signal), forked JVM is killed after timing out waiting period. http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 @@ - org.apache.maven.surefire - surefire-logger-api - - org.apache.maven maven-project http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 962a5bf..397d5d9 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,9 @@ 2.2.1 3.3 + 3.5 + 2.5 + 0.9 scm:git:https://git-wip-us.apache.org/repos/asf/maven-surefire.git surefire-archives/surefire-LATEST @@ -105,12 +108,12 @@ org.apache.commons commons-lang3 - 3.1 + ${commonsLang3Version} commons-io commons-io - 2.2 + ${commonsIoVersion} org.apache.maven.surefire @@ -215,7 +218,13 @@ org.apache.maven.shared maven-shared-utils - 0.9 + ${mavenSharedUtilsVersion} + + + com.google.code.findbugs + jsr305 + + org.apache.maven.shared @@ -377,7 +386,7 @@ maven-shade-plugin - 1.5 + 3.0.0 maven-plugin-plugin http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 @@ org.apache.maven.shared maven-shared-utils + + com.google.code.findbugs + jsr305 + provided + @@ -74,7 +79,6 @@ org.apache.maven.shared:maven-shared-utils - commons-lang:commons-lang @@ -82,10 +86,6 @@ org.apache.maven.shared org.apache.maven.surefire.shade.org.apache.maven.shared - - org.apache.commons.lang - org.apache.maven.surefire.shade.org.apache.commons.lang - http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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..c3d80ea 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 @@ -438,7 +438,7 @@ public final class CommandReader DumpErrorSingleton.getSingleton().dumpStreamException( e, msg ); exitByConfiguration(); - // does not go to finally + // does not go to finally for non-default config: Shutdown.EXIT or Shutdown.KILL } } catch ( IOException e ) @@ -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/89e1e25d/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java index 6844dda..a0dcd66 100644 --- a/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java +++ b/surefire-api/src/main/java/org/apache/maven/surefire/util/ReflectionUtils.java @@ -28,10 +28,6 @@ import java.lang.reflect.Method; */ public final class ReflectionUtils { - private static final Class[] NO_ARGS = new Class[0]; - - private static final Object[] NO_ARGS_VALUES = new Object[0]; - private ReflectionUtils() { throw new IllegalStateException( "no instantiable constructor" ); @@ -68,8 +64,8 @@ public final class ReflectionUtils public static Object invokeGetter( Object instance, String methodName ) { - final Method method = getMethod( instance, methodName, NO_ARGS ); - return invokeMethodWithArray( instance, method, NO_ARGS_VALUES ); + final Method method = getMethod( instance, methodName ); + return invokeMethodWithArray( instance, method ); } public static Constructor getConstructor( Class clazz, Class... arguments ) @@ -245,4 +241,45 @@ public final class ReflectionUtils throw new SurefireReflectionException( e ); } } + + /** + * Invoker of public static no-argument method. + * + * @param clazz class on which public static no-argument {@code methodName} is invoked + * @param methodName public static no-argument method to be called + * @return value returned by {@code methodName} + * @throws RuntimeException if no such method found + * @throws SurefireReflectionException if the method could not be called or threw an exception + */ + public static Object invokeStaticMethod( Class clazz, String methodName ) + { + Method method = getMethod( clazz, methodName ); + return invokeMethodWithArray( null, method ); + } + + /** + * Method chain invoker. + * + * @param firstStaticClass class to start method chain + * @param noArgMethodNames chain of public methods to call + * @param fallback returned value if a chain could not be invoked due to an error + * @return successfully returned value from the last method call; {@code fallback} otherwise + */ + public static Object invokeMethodChain( Class firstStaticClass, String[] noArgMethodNames, Object fallback ) + { + Object obj = null; + try + { + for ( int i = 0, len = noArgMethodNames.length; i < len; i++ ) + { + String methodName = noArgMethodNames[i]; + obj = i == 0 ? invokeStaticMethod( firstStaticClass, methodName ) : invokeGetter( obj, methodName ); + } + return obj; + } + catch ( RuntimeException e ) + { + return fallback; + } + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java index dbf46ea..9558bf8 100644 --- a/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java +++ b/surefire-api/src/test/java/org/apache/maven/JUnit4SuiteTest.java @@ -32,6 +32,7 @@ import org.apache.maven.surefire.testset.FundamentalFilterTest; import org.apache.maven.surefire.testset.ResolvedTestTest; import org.apache.maven.surefire.testset.TestListResolverTest; import org.apache.maven.surefire.util.DefaultDirectoryScannerTest; +import org.apache.maven.surefire.util.ReflectionUtilsTest; import org.apache.maven.surefire.util.RunOrderCalculatorTest; import org.apache.maven.surefire.util.RunOrderTest; import org.apache.maven.surefire.util.ScanResultTest; @@ -70,7 +71,8 @@ import org.junit.runners.Suite; SpecificTestClassFilterTest.class, FundamentalFilterTest.class, SystemUtilsTest.class, - ImmutableMapTest.class + ImmutableMapTest.class, + ReflectionUtilsTest.class } ) @RunWith( Suite.class ) public class JUnit4SuiteTest http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java new file mode 100644 index 0000000..89bc205 --- /dev/null +++ b/surefire-api/src/test/java/org/apache/maven/surefire/util/ReflectionUtilsTest.java @@ -0,0 +1,110 @@ +package org.apache.maven.surefire.util; + +/* + * 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 static org.fest.assertions.Assertions.assertThat; + +/** + * Unit test for {@link ReflectionUtils}. + * + * @author Tibor Digana (tibor17) + * @since 2.20.1 + */ +public class ReflectionUtilsTest +{ + @Test(expected = RuntimeException.class) + public void shouldNotInvokeStaticMethod() + { + ReflectionUtils.invokeStaticMethod( ReflectionUtilsTest.class, "notCallable" ); + } + + @Test + public void shouldInvokeStaticMethod() + { + Object o = ReflectionUtils.invokeStaticMethod( ReflectionUtilsTest.class, "callable" ); + assertThat( o ) + .isEqualTo( 3L ); + } + + @Test + public void shouldInvokeMethodChain() + { + String[] chain = { "current", "pid" }; + Object o = ReflectionUtils.invokeMethodChain( A.class, chain, null ); + assertThat( o ) + .isEqualTo( 3L ); + + String[] longChain = { "current", "createB", "pid" }; + o = ReflectionUtils.invokeMethodChain( A.class, longChain, null ); + assertThat( o ) + .isEqualTo( 1L ); + } + + @Test + public void shouldInvokeFallbackOnMethodChain() + { + String[] chain = { "current", "abc" }; + Object o = ReflectionUtils.invokeMethodChain( A.class, chain, 5L ); + assertThat( o ) + .isEqualTo( 5L ); + + String[] longChain = { "current", "createB", "abc" }; + o = ReflectionUtils.invokeMethodChain( A.class, longChain, 6L ); + assertThat( o ) + .isEqualTo( 6L ); + } + + private static void notCallable() + { + } + + public static long callable() + { + return 3L; + } + + public static class A + { + public static A current() + { + return new A(); + } + + public long pid() + { + return 3L; + } + + public B createB() + { + return new B(); + } + } + + public static class B + { + public long pid() + { + return 1L; + } + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/surefire-booter/pom.xml ---------------------------------------------------------------------- diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml index b79cceb..1dc7591 100644 --- a/surefire-booter/pom.xml +++ b/surefire-booter/pom.xml @@ -35,6 +35,20 @@ org.apache.maven.surefire surefire-api + + + org.apache.maven.shared + maven-shared-utils + + + + + org.apache.commons + commons-lang3 + + + commons-io + commons-io @@ -69,13 +83,18 @@ true - commons-lang:commons-lang + org.apache.commons:commons-lang3 + commons-io:commons-io - org.apache.commons.lang - org.apache.maven.surefire.shade.org.apache.commons.lang + org.apache.commons.lang3 + org.apache.maven.surefire.shade.org.apache.commons.lang3 + + + org.apache.commons.io + org.apache.maven.surefire.shade.org.apache.commons.io http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 c21edf8..3551910 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 @@ -56,4 +56,5 @@ public final class BooterConstants public static final String FAIL_FAST_COUNT = "failFastCount"; public static final String SHUTDOWN = "shutdown"; public static final String SYSTEM_EXIT_TIMEOUT = "systemExitTimeout"; + public static final String PLUGIN_PID = "pluginPid"; } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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 8fa760b..75aad1f 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 @@ -58,6 +58,14 @@ public class BooterDeserializer properties = SystemPropertyManager.loadProperties( inputStream ); } + /** + * @return PID of Maven process where plugin is executed; or null if PID could not be determined. + */ + public Long getPluginPid() + { + return properties.getLongProperty( PLUGIN_PID ); + } + public ProviderConfiguration deserialize() { final File reportsDirectory = new File( properties.getProperty( REPORTSDIRECTORY ) ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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..c19a698 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 @@ -73,9 +73,9 @@ import static org.apache.maven.surefire.util.internal.StringUtils.encodeStringFo */ public final class ForkedBooter { - private static final long DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS = 30; - private static final long PING_TIMEOUT_IN_SECONDS = 20; - private static final long ONE_SECOND_IN_MILLIS = 1000; + 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 CommandReader COMMAND_READER = startupMasterProcessReader(); private static volatile ScheduledThreadPoolExecutor jvmTerminator; @@ -89,8 +89,8 @@ public final class ForkedBooter */ public static void main( String... args ) { - final ExecutorService pingScheduler = isDebugging() ? null : listenToShutdownCommands(); final PrintStream originalOut = out; + ExecutorService pingScheduler = null; try { final String tmpDir = args[0]; @@ -99,6 +99,9 @@ public final class ForkedBooter BooterDeserializer booterDeserializer = new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) ); + + pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid() ); + if ( args.length > 3 ) { final String effectiveSystemPropertiesFileName = args[3]; @@ -202,17 +205,43 @@ public final class ForkedBooter return getReader(); } - private static ExecutorService listenToShutdownCommands() + private static ExecutorService listenToShutdownCommands( Long pluginPid ) { 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 ); + PpidChecker checker = new PpidChecker( pluginPid ); + 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 PpidChecker processChecker ) + { + return new Runnable() + { + @Override + public void run() + { + if ( processChecker != null ) + { + try + { + if ( processChecker.canUse() && !processChecker.isProcessAlive() ) + { + kill(); + } + } + catch ( Exception e ) + { + // nothing to do + } + } + } + }; + } + private static CommandListener createPingHandler( final AtomicBoolean pingDone ) { return new CommandListener() @@ -246,22 +275,30 @@ public final class ForkedBooter }; } - private static Runnable createPingJob( final AtomicBoolean pingDone ) + private static Runnable createPingJob( final AtomicBoolean pingDone, final PpidChecker processChecker ) { return new Runnable() { @Override public void run() { - boolean hasPing = pingDone.getAndSet( false ); - if ( !hasPing ) + if ( !canUseNewPingMechanism( processChecker ) ) { - kill(); + boolean hasPing = pingDone.getAndSet( false ); + if ( !hasPing ) + { + kill(); + } } } }; } + private static boolean canUseNewPingMechanism( PpidChecker processChecker ) + { + return processChecker != null && processChecker.canUse(); + } + private static void encodeAndWriteToOutput( String string, PrintStream out ) { byte[] encodeBytes = encodeStringForForkCommunication( string ); @@ -275,14 +312,18 @@ public final class ForkedBooter private static void kill() { + kill( 1 ); + } + + private static void kill( int returnCode ) + { COMMAND_READER.stop(); - Runtime.getRuntime().halt( 1 ); + Runtime.getRuntime().halt( returnCode ); } private static void exit( int returnCode ) { launchLastDitchDaemonShutdownThread( returnCode ); - COMMAND_READER.stop(); System.exit( returnCode ); } @@ -357,8 +398,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( 2 ); return executor; } @@ -370,8 +411,7 @@ public final class ForkedBooter @Override public void run() { - COMMAND_READER.stop(); - Runtime.getRuntime().halt( returnCode ); + kill( returnCode ); } }, systemExitTimeoutInSeconds, SECONDS ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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..1c0b93f --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java @@ -0,0 +1,285 @@ +package org.apache.maven.surefire.booter; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.File; +import java.io.IOException; +import java.util.Queue; +import java.util.Scanner; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.Long.parseLong; +import static java.util.concurrent.TimeUnit.DAYS; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.MINUTES; +import static java.util.regex.Pattern.compile; +import static org.apache.commons.io.IOUtils.closeQuietly; +import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX; +import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS; +import static org.apache.maven.surefire.booter.ProcessInfo.ERR_PROCESS_INFO; +import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO; + +/** + * Recognizes PID of Plugin process and determines lifetime. + * + * @author Tibor Digana (tibor17) + * @since 2.20.1 + */ +final class PpidChecker +{ + private static final String WMIC_CREATION_DATE = "CreationDate"; + + private final Queue destroyableCommands = new ConcurrentLinkedQueue(); + + /** + * The etime is in the form of [[dd-]hh:]mm:ss on Unix like systems. + */ + static final Pattern UNIX_CMD_OUT_PATTERN = compile( "^(((\\d+)-)?(\\d{2}):)?(\\d{2}):(\\d{2})$" ); + + private final long pluginPid; + + private volatile ProcessInfo pluginProcessInfo; + + PpidChecker( long pluginPid ) + { + this.pluginPid = pluginPid; + } + + boolean canUse() + { + return pluginProcessInfo == null ? IS_OS_WINDOWS || IS_OS_UNIX : pluginProcessInfo.isValid(); + } + + /** + * This method can be called only after {@link #canUse()} has returned {@code true}. + * + * @return {@code true} if parent process is alive; {@code false} otherwise + * @throws IllegalStateException if {@link #canUse()} returns {@code false} or {@link ProcessInfo} is erroneous + */ + @SuppressWarnings( "unchecked" ) + boolean isProcessAlive() + { + if ( !canUse() ) + { + throw new IllegalStateException( "irrelevant to call isProcessAlive()" ); + } + + if ( IS_OS_WINDOWS ) + { + ProcessInfo previousPluginProcessInfo = pluginProcessInfo; + pluginProcessInfo = windows(); + if ( pluginProcessInfo.hasError() ) + { + throw new IllegalStateException( "error to read process" ); + } + // let's compare creation time, should be same unless killed or PID is reused by OS into another process + return pluginProcessInfo.isValid() + && ( previousPluginProcessInfo == null + || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo ) ); + } + else if ( IS_OS_UNIX ) + { + ProcessInfo previousPluginProcessInfo = pluginProcessInfo; + pluginProcessInfo = unix(); + if ( pluginProcessInfo.hasError() ) + { + throw new IllegalStateException( "error to read process" ); + } + // let's compare elapsed time, should be greater or equal if parent process is the same and still alive + return pluginProcessInfo.isValid() + && ( previousPluginProcessInfo == null + || pluginProcessInfo.isTimeEqualTo( previousPluginProcessInfo ) + || pluginProcessInfo.isTimeAfter( previousPluginProcessInfo ) ); + } + + throw new IllegalStateException(); + } + + // https://www.freebsd.org/cgi/man.cgi?ps(1) + // etimes elapsed running time, in decimal integer seconds + + // http://manpages.ubuntu.com/manpages/xenial/man1/ps.1.html + // etimes elapsed time since the process was started, in seconds. + + // http://hg.openjdk.java.net/jdk7/jdk7/jdk/file/9b8c96f96a0f/test/java/lang/ProcessBuilder/Basic.java#L167 + ProcessInfo unix() + { + ProcessInfoConsumer reader = new ProcessInfoConsumer() + { + @Override + ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo ) + { + if ( !previousProcessInfo.isValid() ) + { + Matcher matcher = UNIX_CMD_OUT_PATTERN.matcher( line ); + if ( matcher.matches() ) + { + long pidUptime = fromDays( matcher ) + + fromHours( matcher ) + + fromMinutes( matcher ) + + fromSeconds( matcher ); + return ProcessInfo.unixProcessInfo( pluginPid, pidUptime ); + } + } + return previousProcessInfo; + } + }; + + return reader.execute( "/bin/sh", "-c", unixPathToPS() + " -o etime= -p " + pluginPid ); + } + + ProcessInfo windows() + { + ProcessInfoConsumer reader = new ProcessInfoConsumer() + { + private boolean hasHeader; + + @Override + ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo ) + { + if ( !previousProcessInfo.isValid() ) + { + StringTokenizer args = new StringTokenizer( line ); + if ( args.countTokens() == 1 ) + { + if ( hasHeader ) + { + String startTimestamp = args.nextToken(); + return ProcessInfo.windowsProcessInfo( pluginPid, startTimestamp ); + } + else + { + hasHeader = WMIC_CREATION_DATE.equals( args.nextToken() ); + } + } + } + return previousProcessInfo; + } + }; + String pid = String.valueOf( pluginPid ); + return reader.execute( "CMD", "/A", "/X", "/C", + "wmic process where (ProcessId=" + pid + ") get " + WMIC_CREATION_DATE + ); + } + + void destroyActiveCommands() + { + for ( Process p = destroyableCommands.poll(); p != null; p = destroyableCommands.poll() ) + { + p.destroy(); + } + } + + static String unixPathToPS() + { + return new File( "/usr/bin/ps" ).canExecute() ? "/usr/bin/ps" : "/bin/ps"; + } + + static long fromDays( Matcher matcher ) + { + String s = matcher.group( 3 ); + return s == null ? 0L : DAYS.toSeconds( parseLong( s ) ); + } + + static long fromHours( Matcher matcher ) + { + String s = matcher.group( 4 ); + return s == null ? 0L : HOURS.toSeconds( parseLong( s ) ); + } + + static long fromMinutes( Matcher matcher ) + { + String s = matcher.group( 5 ); + return s == null ? 0L : MINUTES.toSeconds( parseLong( s ) ); + } + + static long fromSeconds( Matcher matcher ) + { + String s = matcher.group( 6 ); + return s == null ? 0L : parseLong( s ); + } + + private static void checkValid( Scanner scanner ) + throws IOException + { + IOException exception = scanner.ioException(); + if ( exception != null ) + { + throw exception; + } + } + + /** + * Reads standard output from {@link Process}. + *
+ * The artifact maven-shared-utils has non-daemon Threads which is an issue in Surefire to satisfy System.exit. + * This implementation is taylor made without using any Thread. + * It's easy to destroy Process from other Thread. + */ + private abstract class ProcessInfoConsumer + { + abstract ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo ); + + ProcessInfo execute( String... command ) + { + ProcessBuilder processBuilder = new ProcessBuilder( command ); + processBuilder.redirectErrorStream( true ); + Process process = null; + ProcessInfo processInfo = INVALID_PROCESS_INFO; + try + { + process = processBuilder.start(); + destroyableCommands.add( process ); + Scanner scanner = new Scanner( process.getInputStream() ); + while ( scanner.hasNextLine() ) + { + String line = scanner.nextLine().trim(); + processInfo = consumeLine( line, processInfo ); + } + checkValid( scanner ); + int exitCode = process.waitFor(); + return exitCode == 0 ? processInfo : ERR_PROCESS_INFO; + } + catch ( IOException e ) + { + return ERR_PROCESS_INFO; + } + catch ( InterruptedException e ) + { + return ERR_PROCESS_INFO; + } + finally + { + if ( process != null ) + { + destroyableCommands.remove( process ); + process.destroy(); + closeQuietly( process.getInputStream() ); + closeQuietly( process.getErrorStream() ); + closeQuietly( process.getOutputStream() ); + } + } + } + } + +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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..4066e84 --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java @@ -0,0 +1,110 @@ +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 static org.apache.maven.surefire.util.internal.ObjectUtils.requireNonNull; + +/** + * Immutable object which encapsulates PID and elapsed time (Unix) or start time (Windows). + *
+ * Methods + * ({@link #getPID()}, {@link #getTime()}, {@link #isTimeAfter(ProcessInfo)}, {@link #isTimeEqualTo(ProcessInfo)}) + * throw {@link IllegalStateException} if {@link #isValid()} returns {@code false}. + * + * @author Tibor Digana (tibor17) + * @since 2.20.1 + */ +final class ProcessInfo +{ + static final ProcessInfo INVALID_PROCESS_INFO = new ProcessInfo( null, null, false ); + static final ProcessInfo ERR_PROCESS_INFO = new ProcessInfo( null, null, true ); + + /** + * On Unix we do not get PID due to the command is interested only to etime of PPID: + *
+ *
/bin/ps -o etime= -p 123
+ */ + static ProcessInfo unixProcessInfo( long pid, long etime ) + { + return new ProcessInfo( pid, etime, false ); + } + + static ProcessInfo windowsProcessInfo( long pid, String startTimestamp ) + { + return new ProcessInfo( pid, requireNonNull( startTimestamp, "startTimestamp is NULL" ), false ); + } + + private final Long pid; + private final Comparable time; + private final boolean error; + + private ProcessInfo( Long pid, Comparable time, boolean error ) + { + this.pid = pid; + this.time = time; + this.error = error; + } + + boolean hasError() + { + return error; + } + + boolean isValid() + { + return !error && pid != null && time != null; + } + + long getPID() + { + checkValid(); + return pid; + } + + Comparable getTime() + { + checkValid(); + return time; + } + + @SuppressWarnings( "unchecked" ) + boolean isTimeEqualTo( ProcessInfo that ) + { + checkValid(); + that.checkValid(); + return this.time.compareTo( that.time ) == 0; + } + + @SuppressWarnings( "unchecked" ) + boolean isTimeAfter( ProcessInfo that ) + { + checkValid(); + that.checkValid(); + return this.time.compareTo( that.time ) > 0; + } + + private void checkValid() + { + if ( !isValid() ) + { + throw new IllegalStateException( "invalid process info" ); + } + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java index 41b4850..7cf04bc 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PropertiesWrapper.java @@ -70,6 +70,12 @@ public class PropertiesWrapper return Integer.parseInt( properties.get( propertyName ) ); } + public Long getLongProperty( String propertyName ) + { + String number = properties.get( propertyName ); + return number == null ? null : Long.parseLong( number ); + } + public File getFileProperty( String key ) { final String property = getProperty( key ); http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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/89e1e25d/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..5849393 --- /dev/null +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java @@ -0,0 +1,131 @@ +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.lang.management.ManagementFactory; +import java.util.regex.Matcher; + +import static org.apache.commons.lang3.SystemUtils.IS_OS_UNIX; +import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS; +import static org.fest.assertions.Assertions.assertThat; +import static org.junit.Assume.assumeTrue; + +/** + * Testing {@link PpidChecker} on a platform. + * + * @author Tibor Digana (tibor17) + * @since 2.20.1 + */ +public class PpidCheckerTest +{ + @Test + public void shouldHavePpidAsWindows() + { + assumeTrue( IS_OS_WINDOWS ); + + long expectedPid = Long.parseLong( ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim() ); + + PpidChecker checker = new PpidChecker( expectedPid ); + ProcessInfo processInfo = checker.windows(); + + assertThat( processInfo ) + .isNotNull(); + + assertThat( checker.canUse() ) + .isTrue(); + + assertThat( checker.isProcessAlive() ) + .isTrue(); + + assertThat( processInfo.getPID() ) + .isEqualTo( expectedPid ); + + assertThat( processInfo.getTime() ) + .isNotNull(); + } + + @Test + public void shouldHavePpidAsUnix() + { + assumeTrue( IS_OS_UNIX ); + + long expectedPid = Long.parseLong( ManagementFactory.getRuntimeMXBean().getName().split( "@" )[0].trim() ); + + PpidChecker checker = new PpidChecker( expectedPid ); + ProcessInfo processInfo = checker.unix(); + + assertThat( processInfo ) + .isNotNull(); + + assertThat( checker.canUse() ) + .isTrue(); + + assertThat( checker.isProcessAlive() ) + .isTrue(); + + assertThat( processInfo.getPID() ) + .isEqualTo( expectedPid ); + + assertThat( processInfo.getTime() ) + .isNotNull(); + } + + @Test + public void shouldParseEtime() + { + Matcher m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "38" ); + assertThat( m.matches() ) + .isFalse(); + + m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "05:38" ); + assertThat( m.matches() ) + .isTrue(); + assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L ); + assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 0L ); + assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L ); + assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L ); + + m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "00:05:38" ); + assertThat( m.matches() ) + .isTrue(); + assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L ); + assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 0L ); + assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L ); + assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L ); + + m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "01:05:38" ); + assertThat( m.matches() ) + .isTrue(); + assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 0L ); + assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 3600L ); + assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L ); + assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L ); + + m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "02-01:05:38" ); + assertThat( m.matches() ) + .isTrue(); + assertThat( PpidChecker.fromDays( m ) ).isEqualTo( 2 * 24 * 3600L ); + assertThat( PpidChecker.fromHours( m ) ).isEqualTo( 3600L ); + assertThat( PpidChecker.fromMinutes( m ) ).isEqualTo( 300L ); + assertThat( PpidChecker.fromSeconds( m ) ).isEqualTo( 38L ); + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/89e1e25d/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..f051c1c 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,9 @@ 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_OSX; import static org.fest.assertions.Assertions.assertThat; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; @@ -46,8 +47,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_OSX ); } @Test