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 D332F200CBD for ; Thu, 6 Jul 2017 21:06:57 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id D199716731A; Thu, 6 Jul 2017 19:06:57 +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 27C7F167318 for ; Thu, 6 Jul 2017 21:06:56 +0200 (CEST) Received: (qmail 90065 invoked by uid 500); 6 Jul 2017 19:06:55 -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 90056 invoked by uid 99); 6 Jul 2017 19:06:55 -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; Thu, 06 Jul 2017 19:06:55 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 364C8DFBC6; Thu, 6 Jul 2017 19:06:55 +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: <8b16e388a10647eaaa17f36d25adf461@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: maven-surefire git commit: [SUREFIRE-1302] Surefire does not wait long enough for the forked VM and assumes it to be dead [Forced Update!] Date: Thu, 6 Jul 2017 19:06:55 +0000 (UTC) archived-at: Thu, 06 Jul 2017 19:06:58 -0000 Repository: maven-surefire Updated Branches: refs/heads/SUREFIRE-1302_3 11e49f0c9 -> ee5e099ae (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/ee5e099a Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/ee5e099a Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/ee5e099a Branch: refs/heads/SUREFIRE-1302_3 Commit: ee5e099ae053fff0b20c9cacf159fbc8fc07de62 Parents: 7176d3c Author: Tibor17 Authored: Sun Jun 25 23:16:04 2017 +0200 Committer: Tibor17 Committed: Thu Jul 6 21:06:44 2017 +0200 ---------------------------------------------------------------------- maven-failsafe-plugin/pom.xml | 17 - maven-surefire-common/pom.xml | 6 + .../surefire/report/FileReporterUtils.java | 11 +- maven-surefire-plugin/pom.xml | 17 - .../src/site/apt/examples/shutdown.apt.vm | 9 + maven-surefire-report-plugin/pom.xml | 4 - pom.xml | 17 +- surefire-api/pom.xml | 10 +- .../maven/surefire/booter/CommandReader.java | 4 +- surefire-booter/pom.xml | 25 +- .../maven/surefire/booter/ForkedBooter.java | 71 +++- .../maven/surefire/booter/PpidChecker.java | 382 +++++++++++++++++++ .../maven/surefire/booter/ProcessInfo.java | 77 ++++ .../maven/surefire/booter/JUnit4SuiteTest.java | 3 +- .../maven/surefire/booter/PpidCheckerTest.java | 182 +++++++++ ...urefire1295AttributeJvmCrashesToTestsIT.java | 6 +- 16 files changed, 771 insertions(+), 70 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/ee5e099a/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/ee5e099a/maven-surefire-common/pom.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml index 4064bc2..e911a4c 100644 --- a/maven-surefire-common/pom.xml +++ b/maven-surefire-common/pom.xml @@ -93,6 +93,7 @@ com.google.code.findbugs jsr305 + provided org.apache.maven.shared @@ -192,6 +193,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 +205,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/ee5e099a/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/ee5e099a/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/ee5e099a/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..4c4a0a3 100644 --- a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm +++ b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm @@ -39,6 +39,15 @@ Shutdown of Forked JVM * Pinging forked JVM + << Since ${thisPlugin} Plugin 2.20.1 ping is platform dependent and fallbacks to old mechanism if platform is + not recognized or native commands fail in Java. >> + + Simply the mechanism checks the <<>> still exists and it is not reused by OS in another application. + If parent (normally Maven) process has died, the forked JVM is killed. + + + << 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. If the master process is killed (received SIGKILL signal) or shutdown http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/ee5e099a/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/ee5e099a/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/ee5e099a/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/ee5e099a/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/ee5e099a/surefire-booter/pom.xml ---------------------------------------------------------------------- diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml index b79cceb..65802a3 100644 --- a/surefire-booter/pom.xml +++ b/surefire-booter/pom.xml @@ -36,6 +36,14 @@ org.apache.maven.surefire surefire-api + + org.apache.commons + commons-lang3 + + + commons-io + commons-io + @@ -50,6 +58,7 @@ + -DPpidCheckerTest.args=shouldFindPid true **/JUnit4SuiteTest.java @@ -69,13 +78,23 @@ true - commons-lang:commons-lang + org.apache.commons:commons-lang3 + commons-io:commons-io + org.apache.maven.shared:maven-shared-utils - 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 + + + org.apache.maven.shared.utils + org.apache.maven.surefire.shade.org.apache.maven.shared.utils http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/ee5e099a/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..00196a9 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,7 +37,9 @@ 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; @@ -89,13 +91,15 @@ 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]; final String dumpFileName = args[1]; final String surefirePropsFileName = args[2]; + PpidChecker.uniqueCommandLineToken = surefirePropsFileName; + pingScheduler = isDebugging() ? null : listenToShutdownCommands(); BooterDeserializer booterDeserializer = new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) ); @@ -207,12 +211,51 @@ 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 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 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 createProcessCheckerJob() + { + return new Callable() + { + @Override + public PpidChecker call() throws Exception + { + 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 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 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/ee5e099a/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..1e0819c --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java @@ -0,0 +1,382 @@ +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.cli.CommandLineException; +import org.apache.maven.shared.utils.cli.Commandline; +import org.apache.maven.shared.utils.cli.StreamConsumer; + +import java.io.File; +import java.lang.management.ManagementFactory; +import java.util.Locale; +import java.util.StringTokenizer; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static java.lang.Character.isDigit; +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.lang3.SystemUtils.IS_OS_UNIX; +import static org.apache.commons.lang3.SystemUtils.IS_OS_WINDOWS; +import static org.apache.maven.shared.utils.cli.CommandLineUtils.executeCommandLine; +import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO; + +/** + * Recognizes PPID. Determines lifetime of parent process. + *
+ * This class cannot use maven-shared-utils because we need to run /bin/bash and not /bin/sh on Unix, and + * cmd /a /x /c instead of cmd /x /c on Windows. + * + * @author Tibor Digana (tibor17) + * @since 2.20.1 + */ +final class PpidChecker +{ + private static final String WMIC_CL = "CommandLine"; + + private static final String WMIC_PID = "ProcessId"; + + 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[] WINDOWS_PID_CMD = + { "wmic", "process", "where", "(Name='java.exe')", "get", WMIC_PID, ",", WMIC_CL }; + + private static final String UNIX_CMD1 = "/usr/bin/ps -o etime= -p $PPID"; + + private static final String UNIX_CMD2 = "/bin/ps -o etime= -p $PPID"; + + static final Pattern UNIX_CMD_OUT_PATTERN = compile( "^(((\\d+)-)?(\\d{2}))?:?(\\d{2}):(\\d{2})$" ); + + private static final Pattern NUMBER_PATTERN = compile( "^\\d+$" ); + + static volatile String uniqueCommandLineToken; + + private final ProcessInfo parentProcessInfo; + + PpidChecker() + { + ProcessInfo parentProcess = INVALID_PROCESS_INFO; + if ( IS_OS_WINDOWS ) + { + String pid = pid(); + if ( pid == null && uniqueCommandLineToken != null ) + { + pid = pidOnWindows(); + } + + 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(); + } + parentProcessInfo = parentProcess.isValid() ? parentProcess : INVALID_PROCESS_INFO; + } + + boolean canUse() + { + return parentProcessInfo.isValid(); + } + + @SuppressWarnings( "unchecked" ) + 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() && pp.getTime().compareTo( parentProcessInfo.getTime() ) == 0; + } + else if ( IS_OS_UNIX ) + { + ProcessInfo pp = unix(); + // let's compare elapsed time, should be greater or equal if parent process is the same and still alive + return pp.isValid() && pp.getTime().compareTo( parentProcessInfo.getTime() ) >= 0; + } + + 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 + static ProcessInfo unix() + { + Commandline cli = new Commandline(); + cli.getShell().setQuotedArgumentsEnabled( false ); + cli.createArg().setLine( new File( "/usr/bin/ps" ).canExecute() ? UNIX_CMD1 : UNIX_CMD2 ); + final AtomicReference processInfo = new AtomicReference( INVALID_PROCESS_INFO ); + try + { + final int exitCode = executeCommandLine( cli, new StreamConsumer() + { + @Override + public void consumeLine( String line ) + { + if ( processInfo.get().isValid() ) + { + return; + } + 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.set( ProcessInfo.unixProcessInfo( pidUptime ) ); + } + } + } + }, null + ); + return exitCode == 0 ? processInfo.get() : INVALID_PROCESS_INFO; + } + catch ( CommandLineException e ) + { + return INVALID_PROCESS_INFO; + } + } + + 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 String pidOnWindows() + { + final AtomicReference pid = new AtomicReference(); + Commandline cli = new Commandline(); + cli.getShell().setQuotedArgumentsEnabled( false ); + cli.addArguments( WINDOWS_PID_CMD ); + try + { + final int exitCode = executeCommandLine( cli, new StreamConsumer() + { + private boolean hasHeader; + private boolean isPidFirst; + + @Override + public void consumeLine( String line ) + { + line = line.trim(); + if ( line.isEmpty() ) + { + return; + } + + if ( hasHeader ) + { + if ( line.contains( uniqueCommandLineToken ) ) + { + String extractedPid = + isPidFirst ? extractNumberFromBegin( line ) : extractNumberFromEnd( line ); + pid.set( extractedPid ); + } + } + else + { + StringTokenizer args = new StringTokenizer( line ); + if ( args.countTokens() == 2 ) + { + String arg0 = args.nextToken(); + String arg1 = args.nextToken(); + isPidFirst = WMIC_PID.equals( arg0 ); + hasHeader = WMIC_PID.equals( arg0 ) || WMIC_CL.equals( arg0 ); + hasHeader &= WMIC_PID.equals( arg1 ) || WMIC_CL.equals( arg1 ); + } + } + } + }, null + ); + return exitCode == 0 ? pid.get() : null; + } + catch ( CommandLineException e ) + { + return null; + } + } + + static ProcessInfo windows( final String pid ) + { + Commandline cli = new Commandline(); + cli.getShell().setQuotedArgumentsEnabled( false ); + cli.createArg().setLine( String.format( Locale.ROOT, WINDOWS_CMD, pid ) ); + + final AtomicReference processInfo = new AtomicReference( INVALID_PROCESS_INFO ); + try + { + final int exitCode = executeCommandLine( cli, new StreamConsumer() + { + private boolean hasHeader; + private boolean isStartTimestampFirst; + + @Override + public void consumeLine( String line ) + { + if ( processInfo.get().isValid() ) + { + return; + } + + line = line.trim(); + + if ( line.isEmpty() ) + { + return; + } + + if ( hasHeader ) + { + StringTokenizer args = new StringTokenizer( line ); + if ( args.countTokens() == 2 ) + { + if ( isStartTimestampFirst ) + { + String startTimestamp = args.nextToken(); + String ppid = args.nextToken(); + processInfo.set( ProcessInfo.windowsProcessInfo( pid, startTimestamp, ppid ) ); + } + else + { + String ppid = args.nextToken(); + String startTimestamp = args.nextToken(); + processInfo.set( ProcessInfo.windowsProcessInfo( 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 = WMIC_CREATION_DATE.equals( arg0 ) || WMIC_PPID.equals( arg0 ); + hasHeader &= WMIC_CREATION_DATE.equals( arg1 ) || WMIC_PPID.equals( arg1 ); + } + } + } + }, null + ); + return exitCode == 0 ? processInfo.get() : INVALID_PROCESS_INFO; + } + catch ( CommandLineException e ) + { + return INVALID_PROCESS_INFO; + } + } + + 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 ) ); + } + + private static long fromMinutes( Matcher matcher ) + { + String s = matcher.group( 5 ); + return s == null ? 0L : MINUTES.toSeconds( parseLong( s ) ); + } + + private static long fromSeconds( Matcher matcher ) + { + String s = matcher.group( 6 ); + return s == null ? 0L : parseLong( s ); + } + + static String extractNumberFromBegin( String line ) + { + StringBuilder number = new StringBuilder(); + for ( int i = 0, len = line.length(); i < len; i++ ) + { + char c = line.charAt( i ); + if ( isDigit( c ) ) + { + number.append( c ); + } + else + { + break; + } + } + return number.toString(); + } + + static String extractNumberFromEnd( String line ) + { + StringBuilder number = new StringBuilder(); + for ( int i = line.length() - 1; i >= 0; i-- ) + { + char c = line.charAt( i ); + if ( isDigit( c ) ) + { + number.insert( 0, c ); + } + else + { + break; + } + } + return number.toString(); + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/ee5e099a/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..a300658 --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java @@ -0,0 +1,77 @@ +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 Tibor Digana (tibor17) + * @since 2.20.1 + */ +final class ProcessInfo +{ + static final ProcessInfo INVALID_PROCESS_INFO = new ProcessInfo( null, null, null ); + + /** + * On Unix we do not get PID due to the command is interested only to etime of PPID: + *
+ *
/bin/ps -o etime= -p $PPID
+ */ + static ProcessInfo unixProcessInfo( long etime ) + { + return new ProcessInfo( "pid not needed on Unix", etime, null ); + } + + static ProcessInfo windowsProcessInfo( String pid, String startTimestamp, String ppid ) + { + return new ProcessInfo( pid, startTimestamp, ppid ); + } + + private final String pid; + private final Comparable time; + private final String ppid; + + private ProcessInfo( String pid, Comparable time, String ppid ) + { + this.pid = pid; + this.time = time; + this.ppid = ppid; + } + + boolean isValid() + { + return pid != null && time != null; + } + + String getPID() + { + return pid; + } + + Comparable getTime() + { + return time; + } + + String getPPID() + { + return ppid; + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/ee5e099a/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/ee5e099a/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..8276705 --- /dev/null +++ b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java @@ -0,0 +1,182 @@ +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.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 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 shouldFindPid() + { + assumeTrue( IS_OS_WINDOWS ); + + PpidChecker.uniqueCommandLineToken = "PpidCheckerTest.args=shouldFindPid"; + String pid = PpidChecker.pidOnWindows(); + + assertThat( pid ) + .isNotNull(); + + assertThat( pid ) + .isEqualTo( PpidChecker.pidOnWindows() ); + } + + @Test + public void shouldHavePpidAsUnix() + { + assumeTrue( IS_OS_UNIX ); + + ProcessInfo processInfo = PpidChecker.unix(); + + assertThat( processInfo ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .isNotNull(); + + assertThat( processInfo.getPID() ) + .isEqualTo( "pid not needed on Unix" ); + + 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(); + } + + @Test + public void shouldParseEtime() + { + assertThat( PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "38" ).matches() ) + .isFalse(); + + assertThat( PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "05:38" ).matches() ) + .isTrue(); + + assertThat( PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "00:05:38" ).matches() ) + .isTrue(); + + assertThat( PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "01:05:38" ).matches() ) + .isTrue(); + + assertThat( PpidChecker.UNIX_CMD_OUT_PATTERN.matcher( "02-01:05:38" ).matches() ) + .isTrue(); + } + + @Test + public void shouldExtractNumberFromBegin() + { + String num = PpidChecker.extractNumberFromBegin( "123 abc" ); + assertThat( num ) + .isEqualTo( "123" ); + } + + @Test + public void shouldNotExtractNumberFromBegin() + { + String num = PpidChecker.extractNumberFromBegin( " 123 abc" ); + assertThat( num ) + .isEmpty(); + } + + @Test + public void shouldExtractNumberFromEnd() + { + String num = PpidChecker.extractNumberFromEnd( "abc 123" ); + assertThat( num ) + .isEqualTo( "123" ); + } + + @Test + public void shouldNotExtractNumberFromEnd() + { + String num = PpidChecker.extractNumberFromEnd( "abs 123 " ); + assertThat( num ) + .isEmpty(); + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/ee5e099a/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