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/a39c79bf Tree: http://git-wip-us.apache.org/repos/asf/maven-surefire/tree/a39c79bf Diff: http://git-wip-us.apache.org/repos/asf/maven-surefire/diff/a39c79bf Branch: refs/heads/SUREFIRE-1302_4 Commit: a39c79bf6d78f7351898ed5baf42f95296b14540 Parents: 4de017b Author: Tibor17 <[email protected]> Authored: Wed Jul 19 01:21:41 2017 +0200 Committer: Tibor17 <[email protected]> Committed: Fri Jul 21 15:41:17 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 | 6 +- .../surefire/report/FileReporterUtils.java | 11 +- ...erDeserializerProviderConfigurationTest.java | 3 +- ...terDeserializerStartupConfigurationTest.java | 3 +- 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 ++- .../surefire/util/internal/SystemUtils.java | 99 ----- .../java/org/apache/maven/JUnit4SuiteTest.java | 6 +- .../surefire/util/ReflectionUtilsTest.java | 110 +++++ .../surefire/util/internal/SystemUtilsTest.java | 98 ----- surefire-booter/pom.xml | 25 +- .../maven/surefire/booter/BooterConstants.java | 1 + .../surefire/booter/BooterDeserializer.java | 8 + .../apache/maven/surefire/booter/Classpath.java | 2 - .../maven/surefire/booter/ForkedBooter.java | 420 ++++++++++--------- .../maven/surefire/booter/PpidChecker.java | 295 +++++++++++++ .../maven/surefire/booter/ProcessInfo.java | 109 +++++ .../surefire/booter/PropertiesWrapper.java | 6 + .../maven/surefire/booter/SystemUtils.java | 200 +++++++++ .../maven/surefire/booter/JUnit4SuiteTest.java | 4 +- .../maven/surefire/booter/PpidCheckerTest.java | 144 +++++++ .../maven/surefire/booter/SystemUtilsTest.java | 143 +++++++ ...urefire1295AttributeJvmCrashesToTestsIT.java | 6 +- 32 files changed, 1397 insertions(+), 473 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/maven-failsafe-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/maven-failsafe-plugin/pom.xml b/maven-failsafe-plugin/pom.xml index f42e682..ec48929 100644 --- a/maven-failsafe-plugin/pom.xml +++ b/maven-failsafe-plugin/pom.xml @@ -45,27 +45,10 @@ <dependencies> <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-plugin-api</artifactId> - </dependency> - <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>maven-surefire-common</artifactId> </dependency> <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-api</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven.shared</groupId> - <artifactId>maven-shared-utils</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven.plugin-tools</groupId> - <artifactId>maven-plugin-annotations</artifactId> - <scope>compile</scope> - </dependency> - <dependency> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>${project.version}</version> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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 @@ <maven>2.2.1</maven> </prerequisites> + <properties> + <!-- Override with Jigsaw JDK 9 --> + <test.jdk>${java.home}/../bin/java</test.jdk> + </properties> + <dependencies> <dependency> <groupId>org.apache.maven</groupId> @@ -44,7 +49,6 @@ <dependency> <groupId>org.apache.maven.plugin-tools</groupId> <artifactId>maven-plugin-annotations</artifactId> - <scope>compile</scope> </dependency> <dependency> <groupId>org.apache.maven.surefire</groupId> @@ -93,6 +97,7 @@ <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> + <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.maven.shared</groupId> @@ -160,6 +165,7 @@ <plugin> <artifactId>maven-surefire-plugin</artifactId> <configuration> + <jvm>${test.jdk}</jvm> <redirectTestOutputToFile>true</redirectTestOutputToFile> <includes> <include>**/JUnit4SuiteTest.java</include> @@ -192,6 +198,7 @@ <include>org.apache.maven.shared:maven-shared-utils</include> <include>org.apache.maven.shared:maven-common-artifact-filters</include> <include>commons-io:commons-io</include> + <include>org.apache.commons:commons-lang3</include> </includes> </artifactSet> <relocations> @@ -203,6 +210,10 @@ <pattern>org.apache.commons.io</pattern> <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern> </relocation> + <relocation> + <pattern>org.apache.commons.lang3</pattern> + <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang3</shadedPattern> + </relocation> </relocations> </configuration> </execution> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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/a39c79bf/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/a39c79bf/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..2d2c53a 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 @@ -45,6 +45,7 @@ import org.apache.maven.surefire.booter.Shutdown; import org.apache.maven.surefire.booter.StartupConfiguration; import org.apache.maven.surefire.booter.SurefireBooterForkException; import org.apache.maven.surefire.booter.SurefireExecutionException; +import org.apache.maven.surefire.booter.SystemUtils; import org.apache.maven.surefire.providerapi.SurefireProvider; import org.apache.maven.surefire.report.StackTraceWriter; import org.apache.maven.surefire.suite.RunResult; @@ -114,6 +115,8 @@ import static org.apache.maven.surefire.util.internal.StringUtils.ISO_8859_1; */ public class ForkStarter { + private static final Long PID = SystemUtils.pid(); + private static final String EXECUTION_EXCEPTION = "ExecutionException"; private static final long PING_IN_SECONDS = 10; @@ -550,7 +553,8 @@ public class ForkStarter BooterSerializer booterSerializer = new BooterSerializer( forkConfiguration ); surefireProperties = booterSerializer.serialize( providerProperties, providerConfiguration, - startupConfiguration, testSet, readTestsFromInStream ); + startupConfiguration, testSet, + readTestsFromInStream, PID ); if ( effectiveSystemProperties != null ) { http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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/a39c79bf/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/a39c79bf/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..0cb292c 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, null ); BooterDeserializer booterDeserializer = new BooterDeserializer( new FileInputStream( propsTest ) ); + assertNull( booterDeserializer.getPluginPid() ); return booterDeserializer.getProviderConfiguration(); } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/maven-surefire-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-plugin/pom.xml b/maven-surefire-plugin/pom.xml index 62ec4a7..2a186e3 100644 --- a/maven-surefire-plugin/pom.xml +++ b/maven-surefire-plugin/pom.xml @@ -45,26 +45,9 @@ <dependencies> <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-plugin-api</artifactId> - </dependency> - <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>maven-surefire-common</artifactId> </dependency> - <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-api</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven</groupId> - <artifactId>maven-toolchain</artifactId> - </dependency> - <dependency> - <groupId>org.apache.maven.plugin-tools</groupId> - <artifactId>maven-plugin-annotations</artifactId> - <scope>compile</scope> - </dependency> </dependencies> <build> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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/a39c79bf/maven-surefire-report-plugin/pom.xml ---------------------------------------------------------------------- diff --git a/maven-surefire-report-plugin/pom.xml b/maven-surefire-report-plugin/pom.xml index a4fe7e2..93a2e80 100644 --- a/maven-surefire-report-plugin/pom.xml +++ b/maven-surefire-report-plugin/pom.xml @@ -48,10 +48,6 @@ <dependencies> <dependency> - <groupId>org.apache.maven.surefire</groupId> - <artifactId>surefire-logger-api</artifactId> - </dependency> - <dependency> <groupId>org.apache.maven</groupId> <artifactId>maven-project</artifactId> </dependency> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/pom.xml ---------------------------------------------------------------------- diff --git a/pom.xml b/pom.xml index 962a5bf..397d5d9 100644 --- a/pom.xml +++ b/pom.xml @@ -91,6 +91,9 @@ <mavenVersion>2.2.1</mavenVersion> <!-- <shadedVersion>2.12.4</shadedVersion> commented out due to https://issues.apache.org/jira/browse/MRELEASE-799 --> <mavenPluginPluginVersion>3.3</mavenPluginPluginVersion> + <commonsLang3Version>3.5</commonsLang3Version> + <commonsIoVersion>2.5</commonsIoVersion> + <mavenSharedUtilsVersion>0.9</mavenSharedUtilsVersion> <maven.surefire.scm.devConnection>scm:git:https://git-wip-us.apache.org/repos/asf/maven-surefire.git</maven.surefire.scm.devConnection> <maven.site.path>surefire-archives/surefire-LATEST</maven.site.path> </properties> @@ -105,12 +108,12 @@ <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> - <version>3.1</version> + <version>${commonsLang3Version}</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> - <version>2.2</version> + <version>${commonsIoVersion}</version> </dependency> <dependency> <groupId>org.apache.maven.surefire</groupId> @@ -215,7 +218,13 @@ <dependency> <groupId>org.apache.maven.shared</groupId> <artifactId>maven-shared-utils</artifactId> - <version>0.9</version> + <version>${mavenSharedUtilsVersion}</version> + <exclusions> + <exclusion> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + </exclusion> + </exclusions> </dependency> <dependency> <groupId>org.apache.maven.shared</groupId> @@ -377,7 +386,7 @@ </plugin> <plugin> <artifactId>maven-shade-plugin</artifactId> - <version>1.5</version> + <version>3.0.0</version> </plugin> <plugin> <artifactId>maven-plugin-plugin</artifactId> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/surefire-api/pom.xml ---------------------------------------------------------------------- diff --git a/surefire-api/pom.xml b/surefire-api/pom.xml index 7e407d2..96c5be3 100644 --- a/surefire-api/pom.xml +++ b/surefire-api/pom.xml @@ -40,6 +40,11 @@ <groupId>org.apache.maven.shared</groupId> <artifactId>maven-shared-utils</artifactId> </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <scope>provided</scope> + </dependency> </dependencies> <build> @@ -74,7 +79,6 @@ <artifactSet> <includes> <include>org.apache.maven.shared:maven-shared-utils</include> - <include>commons-lang:commons-lang</include> </includes> </artifactSet> <relocations> @@ -82,10 +86,6 @@ <pattern>org.apache.maven.shared</pattern> <shadedPattern>org.apache.maven.surefire.shade.org.apache.maven.shared</shadedPattern> </relocation> - <relocation> - <pattern>org.apache.commons.lang</pattern> - <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern> - </relocation> </relocations> </configuration> </execution> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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/a39c79bf/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/a39c79bf/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java b/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java deleted file mode 100644 index 7881b5d..0000000 --- a/surefire-api/src/main/java/org/apache/maven/surefire/util/internal/SystemUtils.java +++ /dev/null @@ -1,99 +0,0 @@ -package org.apache.maven.surefire.util.internal; - -/* - * 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.surefire.util.ReflectionUtils; - -import java.lang.reflect.Method; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import static java.lang.Integer.parseInt; -import static java.lang.Math.pow; - -/** - * JDK 9 support. - * - * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> - * @since 2.20.1 - */ -public final class SystemUtils -{ - private static final Pattern JAVA_SPEC_VERSION_PATTERN = Pattern.compile( "(\\d+)(\\.?)(\\d*).*" ); - private static final double JAVA_SPEC_VERSION = javaSpecVersion(); - - public SystemUtils() - { - throw new IllegalStateException( "no instantiable constructor" ); - } - - public static ClassLoader platformClassLoader() - { - if ( JAVA_SPEC_VERSION < 9 ) - { - return null; - } - - return reflectClassLoader( ClassLoader.class, "getPlatformClassLoader" ); - } - - public static double javaSpecVersion() - { - return extractJavaSpecVersion( System.getProperty( "java.specification.version" ) ); - } - - static ClassLoader reflectClassLoader( Class<?> target, String getterMethodName ) - { - try - { - Method getter = ReflectionUtils.getMethod( target, getterMethodName ); - return (ClassLoader) ReflectionUtils.invokeMethodWithArray( null, getter ); - } - catch ( RuntimeException e ) - { - return null; - } - } - - static double extractJavaSpecVersion( String property ) - { - Matcher versionRegexMatcher = JAVA_SPEC_VERSION_PATTERN.matcher( property ); - int groups = versionRegexMatcher.groupCount(); - if ( !versionRegexMatcher.matches() ) - { - throw new IllegalStateException( "Java Spec Version does not match the pattern " - + JAVA_SPEC_VERSION_PATTERN - ); - } - - if ( groups >= 3 ) - { - String majorVersion = versionRegexMatcher.group( 1 ); - String minorVersion = versionRegexMatcher.group( 3 ); - int major = parseInt( majorVersion ); - double minor = minorVersion.isEmpty() ? 0 : parseInt( minorVersion ) / pow( 10, minorVersion.length() ); - return major + minor; - } - else - { - return parseInt( versionRegexMatcher.group( 0 ) ); - } - } -} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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..66cca64 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; @@ -40,7 +41,6 @@ import org.apache.maven.surefire.util.UrlUtilsTest; import org.apache.maven.surefire.util.internal.ConcurrencyUtilsTest; import org.apache.maven.surefire.util.internal.ImmutableMapTest; import org.apache.maven.surefire.util.internal.StringUtilsTest; -import org.apache.maven.surefire.util.internal.SystemUtilsTest; import org.junit.runner.RunWith; import org.junit.runners.Suite; @@ -69,8 +69,8 @@ import org.junit.runners.Suite; UrlUtilsTest.class, 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/a39c79bf/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 <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> + * @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/a39c79bf/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java ---------------------------------------------------------------------- diff --git a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java b/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java deleted file mode 100644 index bbd9b3c..0000000 --- a/surefire-api/src/test/java/org/apache/maven/surefire/util/internal/SystemUtilsTest.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.apache.maven.surefire.util.internal; - -/* - * 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; - -/** - * Test of {@link SystemUtils}. - * - * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> - * @since 2.20.1 - */ -public class SystemUtilsTest -{ - @Test - public void shouldBeJava9() - { - String simpleVersion = "9"; - double version = SystemUtils.extractJavaSpecVersion( simpleVersion ); - assertThat( version ).isEqualTo( 9d ); - } - - @Test - public void shouldBeJava8() - { - String simpleVersion = "1.8"; - double version = SystemUtils.extractJavaSpecVersion( simpleVersion ); - assertThat( version ).isEqualTo( 1.8d ); - } - - @Test - public void shouldBeJavaMajorAndMinor() - { - String simpleVersion = "12.345.6"; - double version = SystemUtils.extractJavaSpecVersion( simpleVersion ); - assertThat( version ).isEqualTo( 12.345d ); - } - - @Test - public void shouldBeCurrentJavaVersion() - { - Double parsedVersion = SystemUtils.javaSpecVersion(); - String expectedVersion = System.getProperty( "java.specification.version" ); - assertThat( parsedVersion.toString() ).isEqualTo( expectedVersion ); - } - - @Test - public void shouldBePlatformClassLoader() - { - ClassLoader cl = SystemUtils.platformClassLoader(); - if ( SystemUtils.javaSpecVersion() < 9 ) - { - assertThat( cl ).isNull(); - } - else - { - assertThat( cl ).isNotNull(); - } - } - - @Test - public void shouldNotFindClassLoader() - { - ClassLoader cl = SystemUtils.reflectClassLoader( getClass(), "_getPlatformClassLoader_" ); - assertThat( cl ).isNull(); - } - - @Test - public void shouldFindClassLoader() - { - ClassLoader cl = SystemUtils.reflectClassLoader( getClass(), "getPlatformClassLoader" ); - assertThat( cl ).isSameAs( ClassLoader.getSystemClassLoader() ); - } - - public static ClassLoader getPlatformClassLoader() - { - return ClassLoader.getSystemClassLoader(); - } -} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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 @@ <dependency> <groupId>org.apache.maven.surefire</groupId> <artifactId>surefire-api</artifactId> + <exclusions> + <exclusion> + <groupId>org.apache.maven.shared</groupId> + <artifactId>maven-shared-utils</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.apache.commons</groupId> + <artifactId>commons-lang3</artifactId> + </dependency> + <dependency> + <groupId>commons-io</groupId> + <artifactId>commons-io</artifactId> </dependency> </dependencies> @@ -69,13 +83,18 @@ <minimizeJar>true</minimizeJar> <artifactSet> <includes> - <include>commons-lang:commons-lang</include> + <include>org.apache.commons:commons-lang3</include> + <include>commons-io:commons-io</include> </includes> </artifactSet> <relocations> <relocation> - <pattern>org.apache.commons.lang</pattern> - <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang</shadedPattern> + <pattern>org.apache.commons.lang3</pattern> + <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.lang3</shadedPattern> + </relocation> + <relocation> + <pattern>org.apache.commons.io</pattern> + <shadedPattern>org.apache.maven.surefire.shade.org.apache.commons.io</shadedPattern> </relocation> </relocations> </configuration> http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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/a39c79bf/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/a39c79bf/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java ---------------------------------------------------------------------- diff --git a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java index 531e7a1..609be0f 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/Classpath.java @@ -19,8 +19,6 @@ package org.apache.maven.surefire.booter; * under the License. */ -import org.apache.maven.surefire.util.internal.SystemUtils; - import java.io.File; import java.net.MalformedURLException; import java.net.URL; http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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..a05b374 100644 --- a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ForkedBooter.java @@ -30,6 +30,7 @@ import org.apache.maven.surefire.testset.TestSetFailedException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; +import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.lang.management.ManagementFactory; @@ -45,14 +46,9 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import static java.lang.Math.max; -import static java.lang.System.err; -import static java.lang.System.out; -import static java.lang.System.setErr; -import static java.lang.System.setOut; import static java.lang.Thread.currentThread; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static java.util.concurrent.TimeUnit.SECONDS; -import static org.apache.maven.surefire.booter.CommandReader.getReader; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_BYE; import static org.apache.maven.surefire.booter.ForkingRunListener.BOOTERCODE_ERROR; import static org.apache.maven.surefire.booter.ForkingRunListener.encode; @@ -73,107 +69,90 @@ 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 CommandReader COMMAND_READER = startupMasterProcessReader(); + 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 volatile ScheduledThreadPoolExecutor jvmTerminator; - private static volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS; + private final CommandReader commandReader = CommandReader.getReader(); + private final PrintStream originalOut = System.out; - /** - * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and - * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown. - * - * @param args Commandline arguments - */ - public static void main( String... args ) - { - final ExecutorService pingScheduler = isDebugging() ? null : listenToShutdownCommands(); - final PrintStream originalOut = out; - try - { - final String tmpDir = args[0]; - final String dumpFileName = args[1]; - final String surefirePropsFileName = args[2]; + private volatile long systemExitTimeoutInSeconds = DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS; - BooterDeserializer booterDeserializer = - new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) ); - if ( args.length > 3 ) - { - final String effectiveSystemPropertiesFileName = args[3]; - setSystemProperties( new File( tmpDir, effectiveSystemPropertiesFileName ) ); - } + private ScheduledThreadPoolExecutor jvmTerminator; + private PingScheduler pingScheduler; + private ProviderConfiguration providerConfiguration; + private StartupConfiguration startupConfiguration; + private Object testSet; - final ProviderConfiguration providerConfiguration = booterDeserializer.deserialize(); - DumpErrorSingleton.getSingleton().init( dumpFileName, providerConfiguration.getReporterConfiguration() ); + private void setupBooter( String tmpDir, String dumpFileName, String surefirePropsFileName, + String effectiveSystemPropertiesFileName ) + throws IOException, SurefireExecutionException + { + BooterDeserializer booterDeserializer = + new BooterDeserializer( createSurefirePropertiesIfFileExists( tmpDir, surefirePropsFileName ) ); + pingScheduler = isDebugging() ? null : listenToShutdownCommands( booterDeserializer.getPluginPid() ); + setSystemProperties( new File( tmpDir, effectiveSystemPropertiesFileName ) ); - final StartupConfiguration startupConfiguration = booterDeserializer.getProviderConfiguration(); - systemExitTimeoutInSeconds = - providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS ); - final TypeEncodedValue forkedTestSet = providerConfiguration.getTestForFork(); - final boolean readTestsFromInputStream = providerConfiguration.isReadTestsFromInStream(); + providerConfiguration = booterDeserializer.deserialize(); + DumpErrorSingleton.getSingleton().init( dumpFileName, providerConfiguration.getReporterConfiguration() ); - final ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration(); - if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() ) - { - classpathConfiguration.trickClassPathWhenManifestOnlyClasspath(); - } + startupConfiguration = booterDeserializer.getProviderConfiguration(); + systemExitTimeoutInSeconds = + providerConfiguration.systemExitTimeout( DEFAULT_SYSTEM_EXIT_TIMEOUT_IN_SECONDS ); - final ClassLoader classLoader = currentThread().getContextClassLoader(); - classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() ); - startupConfiguration.writeSurefireTestClasspathProperty(); + ClasspathConfiguration classpathConfiguration = startupConfiguration.getClasspathConfiguration(); + if ( startupConfiguration.isManifestOnlyJarRequestedAndUsable() ) + { + classpathConfiguration.trickClassPathWhenManifestOnlyClasspath(); + } - final Object testSet; - if ( forkedTestSet != null ) - { - testSet = forkedTestSet.getDecodedValue( classLoader ); - } - else if ( readTestsFromInputStream ) - { - testSet = new LazyTestsToRun( originalOut ); - } - else - { - testSet = null; - } + ClassLoader classLoader = currentThread().getContextClassLoader(); + classLoader.setDefaultAssertionStatus( classpathConfiguration.isEnableAssertions() ); + startupConfiguration.writeSurefireTestClasspathProperty(); + testSet = createTestSet( providerConfiguration.getTestForFork(), + providerConfiguration.isReadTestsFromInStream(), classLoader ); + } - try - { - runSuitesInProcess( testSet, startupConfiguration, providerConfiguration, originalOut ); - } - catch ( InvocationTargetException t ) - { - DumpErrorSingleton.getSingleton().dumpException( t ); - StackTraceWriter stackTraceWriter = + private void execute() + { + try + { + runSuitesInProcess(); + } + catch ( InvocationTargetException t ) + { + DumpErrorSingleton.getSingleton().dumpException( t ); + StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t.getTargetException() ); - StringBuilder stringBuilder = new StringBuilder(); - encode( stringBuilder, stackTraceWriter, false ); - encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" , originalOut ); - } - catch ( Throwable t ) - { - DumpErrorSingleton.getSingleton().dumpException( t ); - StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t ); - StringBuilder stringBuilder = new StringBuilder(); - encode( stringBuilder, stackTraceWriter, false ); - encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n", originalOut ); - } - acknowledgedExit( originalOut, pingScheduler ); + StringBuilder stringBuilder = new StringBuilder(); + encode( stringBuilder, stackTraceWriter, false ); + encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" ); } catch ( Throwable t ) { DumpErrorSingleton.getSingleton().dumpException( t ); - // Just throwing does getMessage() and a local trace - we want to call printStackTrace for a full trace - // noinspection UseOfSystemOutOrSystemErr - t.printStackTrace( err ); - cancelPingScheduler( pingScheduler ); - // noinspection ProhibitedExceptionThrown,CallToSystemExit - exit( 1 ); + StackTraceWriter stackTraceWriter = new LegacyPojoStackTraceWriter( "test subsystem", "no method", t ); + StringBuilder stringBuilder = new StringBuilder(); + encode( stringBuilder, stackTraceWriter, false ); + encodeAndWriteToOutput( ( (char) BOOTERCODE_ERROR ) + ",0," + stringBuilder + "\n" ); } + acknowledgedExit(); } - private static void cancelPingScheduler( final ExecutorService pingScheduler ) + private Object createTestSet( TypeEncodedValue forkedTestSet, boolean readTestsFromCommandReader, ClassLoader cl ) + { + if ( forkedTestSet != null ) + { + return forkedTestSet.getDecodedValue( cl ); + } + else if ( readTestsFromCommandReader ) + { + return new LazyTestsToRun( originalOut ); + } + return null; + } + + private void cancelPingScheduler() { if ( pingScheduler != null ) { @@ -197,23 +176,47 @@ public final class ForkedBooter } } - private static CommandReader startupMasterProcessReader() + private PingScheduler listenToShutdownCommands( Long pluginPid ) { - return getReader(); + commandReader.addShutdownListener( createExitHandler() ); + AtomicBoolean pingDone = new AtomicBoolean( true ); + commandReader.addNoopListener( createPingHandler( pingDone ) ); + + ScheduledExecutorService pingScheduler = createPingScheduler(); + PpidChecker checker = pluginPid == null ? null : new PpidChecker( pluginPid ); + if ( checker != null ) + { + pingScheduler.scheduleWithFixedDelay( processCheckerJob( checker ), 0L, 1L, SECONDS ); + } + pingScheduler.scheduleAtFixedRate( createPingJob( pingDone, checker ), 0L, PING_TIMEOUT_IN_SECONDS, SECONDS ); + + return new PingScheduler( pingScheduler, checker ); } - private static ExecutorService listenToShutdownCommands() + private Runnable processCheckerJob( final PpidChecker pluginProcessChecker ) { - 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 ); - return pingScheduler; + return new Runnable() + { + @Override + public void run() + { + try + { + if ( pluginProcessChecker.canUse() && !pluginProcessChecker.isProcessAlive() + && !pingScheduler.isShutdown() ) + { + kill(); + } + } + catch ( Exception e ) + { + // nothing to do + } + } + }; } - private static CommandListener createPingHandler( final AtomicBoolean pingDone ) + private CommandListener createPingHandler( final AtomicBoolean pingDone ) { return new CommandListener() { @@ -225,7 +228,7 @@ public final class ForkedBooter }; } - private static CommandListener createExitHandler() + private CommandListener createExitHandler() { return new CommandListener() { @@ -239,6 +242,7 @@ public final class ForkedBooter } else if ( shutdown.isExit() ) { + cancelPingScheduler(); exit( 1 ); } // else refers to shutdown=testset, but not used now, keeping reader open @@ -246,98 +250,88 @@ public final class ForkedBooter }; } - private static Runnable createPingJob( final AtomicBoolean pingDone ) + private Runnable createPingJob( final AtomicBoolean pingDone, final PpidChecker pluginProcessChecker ) { return new Runnable() { @Override public void run() { - boolean hasPing = pingDone.getAndSet( false ); - if ( !hasPing ) + if ( !canUseNewPingMechanism( pluginProcessChecker ) ) { - kill(); + boolean hasPing = pingDone.getAndSet( false ); + if ( !hasPing ) + { + kill(); + } } } }; } - private static void encodeAndWriteToOutput( String string, PrintStream out ) + private void encodeAndWriteToOutput( String string ) { byte[] encodeBytes = encodeStringForForkCommunication( string ); //noinspection SynchronizationOnLocalVariableOrMethodParameter - synchronized ( out ) + synchronized ( originalOut ) { - out.write( encodeBytes, 0, encodeBytes.length ); - out.flush(); + originalOut.write( encodeBytes, 0, encodeBytes.length ); + originalOut.flush(); } } - private static void kill() + private void kill() { - COMMAND_READER.stop(); - Runtime.getRuntime().halt( 1 ); + kill( 1 ); } - private static void exit( int returnCode ) + private void kill( int returnCode ) + { + commandReader.stop(); + Runtime.getRuntime().halt( returnCode ); + } + + private void exit( int returnCode ) { launchLastDitchDaemonShutdownThread( returnCode ); - COMMAND_READER.stop(); System.exit( returnCode ); } - private static void acknowledgedExit( PrintStream originalOut, ExecutorService pingScheduler ) + private void acknowledgedExit() { final Semaphore barrier = new Semaphore( 0 ); - COMMAND_READER.addByeAckListener( new CommandListener() - { - @Override - public void update( Command command ) - { - barrier.release(); - } - } + commandReader.addByeAckListener( new CommandListener() + { + @Override + public void update( Command command ) + { + barrier.release(); + } + } ); - encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n", originalOut ); + encodeAndWriteToOutput( ( (char) BOOTERCODE_BYE ) + ",0,BYE!\n" ); launchLastDitchDaemonShutdownThread( 0 ); long timeoutMillis = max( systemExitTimeoutInSeconds * ONE_SECOND_IN_MILLIS, ONE_SECOND_IN_MILLIS ); acquireOnePermit( barrier, timeoutMillis ); - cancelPingScheduler( pingScheduler ); - COMMAND_READER.stop(); + cancelPingScheduler(); + commandReader.stop(); System.exit( 0 ); } - private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis ) - { - try - { - return barrier.tryAcquire( timeoutMillis, MILLISECONDS ); - } - catch ( InterruptedException e ) - { - return true; - } - } - - private static RunResult runSuitesInProcess( Object testSet, StartupConfiguration startupConfiguration, - ProviderConfiguration providerConfiguration, - PrintStream originalSystemOut ) + private RunResult runSuitesInProcess() throws SurefireExecutionException, TestSetFailedException, InvocationTargetException { - final ReporterFactory factory = createForkingReporterFactory( providerConfiguration, originalSystemOut ); - - return invokeProviderInSameClassLoader( testSet, factory, providerConfiguration, true, startupConfiguration, - false ); + ReporterFactory factory = createForkingReporterFactory(); + return invokeProviderInSameClassLoader( factory ); } - private static ReporterFactory createForkingReporterFactory( ProviderConfiguration providerConfiguration, - PrintStream originalSystemOut ) + private ReporterFactory createForkingReporterFactory() { final boolean trimStackTrace = providerConfiguration.getReporterConfiguration().isTrimStackTrace(); - return new ForkingReporterFactory( trimStackTrace, originalSystemOut ); + return new ForkingReporterFactory( trimStackTrace, originalOut ); } - private static synchronized ScheduledThreadPoolExecutor getJvmTerminator() + private synchronized ScheduledThreadPoolExecutor getJvmTerminator() { if ( jvmTerminator == null ) { @@ -345,71 +339,34 @@ public final class ForkedBooter newDaemonThreadFactory( "last-ditch-daemon-shutdown-thread-" + systemExitTimeoutInSeconds + "s" ); jvmTerminator = new ScheduledThreadPoolExecutor( 1, threadFactory ); jvmTerminator.setMaximumPoolSize( 1 ); - return jvmTerminator; - } - else - { - return jvmTerminator; } - } - - private static ScheduledExecutorService createPingScheduler() - { - ThreadFactory threadFactory = newDaemonThreadFactory( "ping-" + PING_TIMEOUT_IN_SECONDS + "s" ); - ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory ); - executor.setMaximumPoolSize( 1 ); - executor.prestartCoreThread(); - return executor; + return jvmTerminator; } @SuppressWarnings( "checkstyle:emptyblock" ) - private static void launchLastDitchDaemonShutdownThread( final int returnCode ) + private void launchLastDitchDaemonShutdownThread( final int returnCode ) { getJvmTerminator().schedule( new Runnable() { @Override public void run() { - COMMAND_READER.stop(); - Runtime.getRuntime().halt( returnCode ); + kill( returnCode ); } }, systemExitTimeoutInSeconds, SECONDS ); } - private static RunResult invokeProviderInSameClassLoader( Object testSet, Object factory, - ProviderConfiguration providerConfig, - boolean insideFork, - StartupConfiguration startupConfig, - boolean restoreStreams ) + private RunResult invokeProviderInSameClassLoader( ReporterFactory factory ) throws TestSetFailedException, InvocationTargetException { - final PrintStream orgSystemOut = out; - final PrintStream orgSystemErr = err; - // Note that System.out/System.err are also read in the "ReporterConfiguration" instantiation - // in createProvider below. These are the same values as here. - - try - { - return createProviderInCurrentClassloader( startupConfig, insideFork, providerConfig, factory ) - .invoke( testSet ); - } - finally - { - if ( restoreStreams && System.getSecurityManager() == null ) - { - setOut( orgSystemOut ); - setErr( orgSystemErr ); - } - } + return createProviderInCurrentClassloader( factory ) + .invoke( testSet ); } - private static SurefireProvider createProviderInCurrentClassloader( StartupConfiguration startupConfiguration, - boolean isInsideFork, - ProviderConfiguration providerConfiguration, - Object reporterManagerFactory ) + private SurefireProvider createProviderInCurrentClassloader( Object reporterManagerFactory ) { - BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory, isInsideFork ); + BaseProviderFactory bpf = new BaseProviderFactory( (ReporterFactory) reporterManagerFactory, true ); bpf.setTestRequest( providerConfiguration.getTestSuiteDefinition() ); bpf.setReporterConfiguration( providerConfiguration.getReporterConfiguration() ); ClassLoader classLoader = currentThread().getContextClassLoader(); @@ -426,6 +383,55 @@ public final class ForkedBooter return (SurefireProvider) instantiateOneArg( classLoader, providerClass, ProviderParameters.class, bpf ); } + /** + * This method is invoked when Surefire is forked - this method parses and organizes the arguments passed to it and + * then calls the Surefire class' run method. <br> The system exit code will be 1 if an exception is thrown. + * + * @param args Commandline arguments + */ + public static void main( String... args ) + { + ForkedBooter booter = new ForkedBooter(); + try + { + booter.setupBooter( args[0], args[1], args[2], args.length > 3 ? args[3] : null ); + booter.execute(); + } + catch ( Throwable t ) + { + DumpErrorSingleton.getSingleton().dumpException( t ); + t.printStackTrace(); + booter.cancelPingScheduler(); + booter.exit( 1 ); + } + } + + private static boolean canUseNewPingMechanism( PpidChecker pluginProcessChecker ) + { + return pluginProcessChecker != null && pluginProcessChecker.canUse(); + } + + private static boolean acquireOnePermit( Semaphore barrier, long timeoutMillis ) + { + try + { + return barrier.tryAcquire( timeoutMillis, MILLISECONDS ); + } + catch ( InterruptedException e ) + { + return true; + } + } + + private static ScheduledExecutorService createPingScheduler() + { + ThreadFactory threadFactory = newDaemonThreadFactory( "ping-" + PING_TIMEOUT_IN_SECONDS + "s" ); + ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor( 1, threadFactory ); + executor.setKeepAliveTime( 3L, SECONDS ); + executor.setMaximumPoolSize( 2 ); + return executor; + } + private static InputStream createSurefirePropertiesIfFileExists( String tmpDir, String propFileName ) throws FileNotFoundException { @@ -444,4 +450,30 @@ public final class ForkedBooter } return false; } + + private static class PingScheduler + { + private final ExecutorService pingScheduler; + private final PpidChecker pluginProcessChecker; + + PingScheduler( ExecutorService pingScheduler, PpidChecker pluginProcessChecker ) + { + this.pingScheduler = pingScheduler; + this.pluginProcessChecker = pluginProcessChecker; + } + + void shutdown() + { + pingScheduler.shutdown(); + if ( pluginProcessChecker != null ) + { + pluginProcessChecker.destroyActiveCommands(); + } + } + + boolean isShutdown() + { + return pingScheduler.isShutdown(); + } + } } http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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..f7eb1df --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/PpidChecker.java @@ -0,0 +1,295 @@ +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 <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> + * @since 2.20.1 + */ +final class PpidChecker +{ + private static final String WMIC_CREATION_DATE = "CreationDate"; + + private final Queue<Process> destroyableCommands = new ConcurrentLinkedQueue<Process>(); + + /** + * 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; + private volatile boolean stopped; + + PpidChecker( long pluginPid ) + { + this.pluginPid = pluginPid; + } + + boolean canUse() + { + return pluginProcessInfo == null + ? IS_OS_WINDOWS || IS_OS_UNIX + : pluginProcessInfo.isValid() && !pluginProcessInfo.isError(); + } + + /** + * 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 the object has been {@link #destroyActiveCommands() destroyed} + */ + @SuppressWarnings( "unchecked" ) + boolean isProcessAlive() + { + if ( !canUse() ) + { + throw new IllegalStateException( "irrelevant to call isProcessAlive()" ); + } + + if ( IS_OS_WINDOWS ) + { + ProcessInfo previousPluginProcessInfo = pluginProcessInfo; + pluginProcessInfo = windows(); + if ( isStopped() || pluginProcessInfo.isError() ) + { + 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 ( isStopped() || pluginProcessInfo.isError() ) + { + 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() + { + stopped = true; + for ( Process p = destroyableCommands.poll(); p != null; p = destroyableCommands.poll() ) + { + p.destroy(); + } + } + + private boolean isStopped() + { + return stopped; + } + + 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}. + * <br> + * 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 : INVALID_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/a39c79bf/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..212e221 --- /dev/null +++ b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessInfo.java @@ -0,0 +1,109 @@ +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). + * <br> + * Methods + * ({@link #getPID()}, {@link #getTime()}, {@link #isTimeAfter(ProcessInfo)}, {@link #isTimeEqualTo(ProcessInfo)}) + * throw {@link IllegalStateException} + * if {@link #isValid()} returns {@code false} or {@link #isError()} returns {@code true}. + * + * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a> + * @since 2.20.1 + */ +final class ProcessInfo +{ + static final ProcessInfo INVALID_PROCESS_INFO = new ProcessInfo( null, null ); + static final ProcessInfo ERR_PROCESS_INFO = new ProcessInfo( null, null ); + + /** + * On Unix we do not get PID due to the command is interested only to etime of PPID: + * <br> + * <pre>/bin/ps -o etime= -p 123</pre> + */ + static ProcessInfo unixProcessInfo( long pid, long etime ) + { + return new ProcessInfo( pid, etime ); + } + + static ProcessInfo windowsProcessInfo( long pid, String startTimestamp ) + { + return new ProcessInfo( pid, requireNonNull( startTimestamp, "startTimestamp is NULL" ) ); + } + + private final Long pid; + private final Comparable time; + + private ProcessInfo( Long pid, Comparable time ) + { + this.pid = pid; + this.time = time; + } + + boolean isValid() + { + return this != INVALID_PROCESS_INFO; + } + + boolean isError() + { + return this == ERR_PROCESS_INFO; + } + + 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() || isError() ) + { + throw new IllegalStateException( "invalid process info" ); + } + } +} http://git-wip-us.apache.org/repos/asf/maven-surefire/blob/a39c79bf/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..d94be71 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 = getProperty( propertyName ); + return number == null ? null : Long.parseLong( number ); + } + public File getFileProperty( String key ) { final String property = getProperty( key );
