This is an automated email from the ASF dual-hosted git repository. tibordigana pushed a commit to branch 1512 in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
commit b67ce44f245e0418268c8d37ef4d807d28c520be Author: Tibor17 <tibordig...@apache.org> AuthorDate: Sun Apr 8 12:24:39 2018 +0200 [SUREFIRE-1512] ProcessInfo for Windows is prone to timezone offset changes --- .../apache/maven/surefire/booter/PpidChecker.java | 74 +++++++++++++++------- .../apache/maven/surefire/booter/ProcessInfo.java | 6 +- 2 files changed, 52 insertions(+), 28 deletions(-) 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 index 32e3ad7..491a786 100644 --- 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 @@ -22,13 +22,16 @@ package org.apache.maven.surefire.booter; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; +import java.text.SimpleDateFormat; +import java.util.Locale; import java.util.Queue; import java.util.Scanner; -import java.util.StringTokenizer; +import java.util.TimeZone; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.regex.Matcher; import java.util.regex.Pattern; +import static java.lang.Integer.parseInt; import static java.lang.Long.parseLong; import static java.util.concurrent.TimeUnit.DAYS; import static java.util.concurrent.TimeUnit.HOURS; @@ -39,6 +42,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.SystemUtils.IS_OS_HP_UX; 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.unixProcessInfo; +import static org.apache.maven.surefire.booter.ProcessInfo.windowsProcessInfo; import static org.apache.maven.surefire.booter.ProcessInfo.ERR_PROCESS_INFO; import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO; @@ -50,6 +55,11 @@ import static org.apache.maven.surefire.booter.ProcessInfo.INVALID_PROCESS_INFO; */ final class PpidChecker { + private static final int MINUTES_TO_MILLIS = 60 * 1000; + // 25 chars https://superuser.com/questions/937380/get-creation-time-of-file-in-milliseconds/937401#937401 + private static final int WMIC_CREATION_DATE_VALUE_LENGTH = 25; + private static final int WMIC_CREATION_DATE_TIMESTAMP_LENGTH = 18; + private static final SimpleDateFormat WMIC_CREATION_DATE_FORMAT = createWindowsCreationDateFormat(); private static final String WMIC_CREATION_DATE = "CreationDate"; private static final String WINDOWS_SYSTEM_ROOT_ENV = "SystemRoot"; private static final String RELATIVE_PATH_TO_WMIC = "System32\\Wbem"; @@ -151,7 +161,7 @@ final class PpidChecker + fromHours( matcher ) + fromMinutes( matcher ) + fromSeconds( matcher ); - return ProcessInfo.unixProcessInfo( pluginPid, pidUptime ); + return unixProcessInfo( pluginPid, pidUptime ); } } return previousProcessInfo; @@ -168,31 +178,36 @@ final class PpidChecker private boolean hasHeader; @Override - ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo ) + ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo ) throws Exception { - if ( !previousProcessInfo.isValid() ) + if ( !previousProcessInfo.isValid() && !line.isEmpty() ) { - StringTokenizer args = new StringTokenizer( line ); - if ( args.countTokens() == 1 ) + if ( hasHeader ) { - if ( hasHeader ) - { - String startTimestamp = args.nextToken(); - return ProcessInfo.windowsProcessInfo( pluginPid, startTimestamp ); - } - else + // now the line is CreationDate, e.g. 20180406142327.741074+120 + if ( line.length() != WMIC_CREATION_DATE_VALUE_LENGTH ) { - hasHeader = WMIC_CREATION_DATE.equals( args.nextToken() ); + throw new IllegalStateException( "WMIC CreationDate should have 25 characters " + + line ); } + String startTimestamp = line.substring( 0, WMIC_CREATION_DATE_TIMESTAMP_LENGTH ); + int indexOfTimeZone = WMIC_CREATION_DATE_VALUE_LENGTH - 4; + long startTimestampMillisUTC = + WMIC_CREATION_DATE_FORMAT.parse( startTimestamp ).getTime() + - parseInt( line.substring( indexOfTimeZone ) ) * MINUTES_TO_MILLIS; + return windowsProcessInfo( pluginPid, startTimestampMillisUTC ); + } + else + { + hasHeader = WMIC_CREATION_DATE.equals( line ); } } return previousProcessInfo; } }; - String pid = String.valueOf( pluginPid ); String wmicPath = hasWmicStandardSystemPath() ? SYSTEM_PATH_TO_WMIC : ""; return reader.execute( "CMD", "/A", "/X", "/C", - wmicPath + "wmic process where (ProcessId=" + pid + ") get " + WMIC_CREATION_DATE + wmicPath + "wmic process where (ProcessId=" + pluginPid + ") get " + WMIC_CREATION_DATE ); } @@ -210,7 +225,7 @@ final class PpidChecker return stopped; } - static String unixPathToPS() + private static String unixPathToPS() { return canExecuteLocalUnixPs() ? "/usr/bin/ps" : "/bin/ps"; } @@ -271,6 +286,20 @@ final class PpidChecker } /** + * The beginning part of Windows WMIC format yyyymmddHHMMSS.xxx <br> + * https://technet.microsoft.com/en-us/library/ee198928.aspx <br> + * We use UTC time zone which avoids DST changes, see SUREFIRE-1512. + * + * @return Windows WMIC format yyyymmddHHMMSS.xxx + */ + private static SimpleDateFormat createWindowsCreationDateFormat() + { + SimpleDateFormat formatter = new SimpleDateFormat( "yyyyMMddHHmmss'.'SSS", Locale.ENGLISH ); + formatter.setTimeZone( TimeZone.getTimeZone( "UTC" ) ); + return formatter; + } + + /** * Reads standard output from {@link Process}. * <br> * The artifact maven-shared-utils has non-daemon Threads which is an issue in Surefire to satisfy System.exit. @@ -286,7 +315,7 @@ final class PpidChecker this.charset = charset; } - abstract ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo ); + abstract ProcessInfo consumeLine( String line, ProcessInfo previousProcessInfo ) throws Exception; ProcessInfo execute( String... command ) { @@ -312,14 +341,11 @@ final class PpidChecker int exitCode = process.waitFor(); return exitCode == 0 ? processInfo : INVALID_PROCESS_INFO; } - catch ( IOException e ) + catch ( Exception e ) { - DumpErrorSingleton.getSingleton().dumpException( e ); - return ERR_PROCESS_INFO; - } - catch ( InterruptedException e ) - { - DumpErrorSingleton.getSingleton().dumpException( e ); + DumpErrorSingleton.getSingleton() + .dumpException( e ); + return ERR_PROCESS_INFO; } finally 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 index 212e221..2e48599 100644 --- 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 @@ -19,8 +19,6 @@ package org.apache.maven.surefire.booter; * 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). * <br> @@ -47,9 +45,9 @@ final class ProcessInfo return new ProcessInfo( pid, etime ); } - static ProcessInfo windowsProcessInfo( long pid, String startTimestamp ) + static ProcessInfo windowsProcessInfo( long pid, long startTimestamp ) { - return new ProcessInfo( pid, requireNonNull( startTimestamp, "startTimestamp is NULL" ) ); + return new ProcessInfo( pid, startTimestamp ); } private final Long pid; -- To stop receiving notification emails like this one, please contact tibordig...@apache.org.