This is an automated email from the ASF dual-hosted git repository.
tibordigana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-surefire.git
The following commit(s) were added to refs/heads/master by this push:
new 4ced57c05 Revert "Replace runing external process and parsing output
with simple Proces…"
4ced57c05 is described below
commit 4ced57c058bee5c68fd1313a401a018c9d0303bb
Author: Tibor Digana <[email protected]>
AuthorDate: Sun Feb 15 00:19:10 2026 +0100
Revert "Replace runing external process and parsing output with simple
Proces…"
This reverts commit 0b190142a3df4cb3dda52825e7fedda59591cbc8.
---
maven-surefire-common/pom.xml | 1 +
.../plugin/surefire/booterclient/Platform.java | 9 +-
.../src/site/apt/examples/shutdown.apt.vm | 6 +-
surefire-booter/pom.xml | 5 -
.../apache/maven/surefire/booter/ForkedBooter.java | 12 +-
.../apache/maven/surefire/booter/PpidChecker.java | 27 +-
.../maven/surefire/booter/ProcessChecker.java | 108 ------
.../surefire/booter/ProcessHandleChecker.java | 238 ------------
.../apache/maven/surefire/booter/ProcessInfo.java | 11 -
.../apache/maven/surefire/booter/SystemUtils.java | 1 -
.../maven/surefire/booter/PpidCheckerTest.java | 432 +++++++++++++++++++++
.../maven/surefire/booter/ProcessCheckerTest.java | 248 ------------
.../surefire/booter/ProcessHandleCheckerTest.java | 205 ----------
13 files changed, 452 insertions(+), 851 deletions(-)
diff --git a/maven-surefire-common/pom.xml b/maven-surefire-common/pom.xml
index 072efcabf..a9feb6dd9 100644
--- a/maven-surefire-common/pom.xml
+++ b/maven-surefire-common/pom.xml
@@ -172,6 +172,7 @@
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
+ <version>2.21.0</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
index e18803142..12d19c5e8 100644
---
a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
+++
b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/Platform.java
@@ -29,7 +29,7 @@
/**
* Loads platform specifics.
- * TODO simplify or remove when Java 8 support is dropped
+ *
* @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a>
* @since 2.20.1
*/
@@ -80,6 +80,11 @@ public Platform withJdkExecAttributesForTests(JdkAttributes
jdk) {
}
private static Callable<Long> pidJob() {
- return SystemUtils::pid;
+ return new Callable<Long>() {
+ @Override
+ public Long call() throws Exception {
+ return SystemUtils.pid();
+ }
+ };
}
}
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 93083858d..92f9ddc6c 100644
--- a/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
+++ b/maven-surefire-plugin/src/site/apt/examples/shutdown.apt.vm
@@ -55,13 +55,9 @@ Shutdown of Forked JVM
[]
- If Java9 is available, the start time of the process is determined by <<<
ProcessHandle.current().info().startInstant() >>>.
-
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 <<< powershell -command "...
Get-CimInstance Win32_Process ..." >>>.
-
- []
+ On Windows the start time is determined using <<< wmic process where
(ProcessId=[PID]) get CreationDate >>>
in the forked JVM.
diff --git a/surefire-booter/pom.xml b/surefire-booter/pom.xml
index 74b055a9d..c24bb7e36 100644
--- a/surefire-booter/pom.xml
+++ b/surefire-booter/pom.xml
@@ -91,11 +91,6 @@
<artifactId>powermock-api-mockito2</artifactId>
<scope>test</scope>
</dependency>
- <dependency>
- <groupId>commons-io</groupId>
- <artifactId>commons-io</artifactId>
- <scope>test</scope>
- </dependency>
</dependencies>
<build>
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 5067509c3..1bcdc8b09 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
@@ -215,7 +215,7 @@ private void closeForkChannel() {
}
private PingScheduler listenToShutdownCommands(String ppid) {
- ProcessChecker ppidChecker = ProcessChecker.of(ppid);
+ PpidChecker ppidChecker = ppid == null ? null : new PpidChecker(ppid);
commandReader.addShutdownListener(createExitHandler(ppidChecker));
AtomicBoolean pingDone = new AtomicBoolean(true);
commandReader.addNoopListener(createPingHandler(pingDone));
@@ -280,7 +280,7 @@ public void update(Command command) {
};
}
- private CommandListener createExitHandler(final ProcessChecker
ppidChecker) {
+ private CommandListener createExitHandler(final PpidChecker ppidChecker) {
return new CommandListener() {
@Override
public void update(Command command) {
@@ -325,7 +325,7 @@ public void update(Command command) {
};
}
- private Runnable createPingJob(final AtomicBoolean pingDone, final
ProcessChecker pluginProcessChecker) {
+ private Runnable createPingJob(final AtomicBoolean pingDone, final
PpidChecker pluginProcessChecker) {
return new Runnable() {
@Override
public void run() {
@@ -515,7 +515,7 @@ private static void run(ForkedBooter booter, String[] args)
{
}
}
- private static boolean canUseNewPingMechanism(ProcessChecker
pluginProcessChecker) {
+ private static boolean canUseNewPingMechanism(PpidChecker
pluginProcessChecker) {
return pluginProcessChecker != null && pluginProcessChecker.canUse();
}
@@ -553,12 +553,12 @@ private static boolean isDebugging() {
private static class PingScheduler {
private final ScheduledExecutorService pingScheduler;
private final ScheduledExecutorService processCheckerScheduler;
- private final ProcessChecker processChecker;
+ private final PpidChecker processChecker;
PingScheduler(
ScheduledExecutorService pingScheduler,
ScheduledExecutorService processCheckerScheduler,
- ProcessChecker processChecker) {
+ PpidChecker processChecker) {
this.pingScheduler = pingScheduler;
this.processCheckerScheduler = processCheckerScheduler;
this.processChecker = processChecker;
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 bfcc70d18..b8891e822 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
@@ -57,18 +57,11 @@
/**
* Recognizes PID of Plugin process and determines lifetime.
- * <p>
- * This implementation uses native commands ({@code ps} on Unix, {@code
powershell} on Windows)
- * to check the parent process status. On Java 9+, consider using {@code
ProcessHandleChecker}
- * instead, which uses the Java {@code ProcessHandle} API and doesn't require
spawning external processes.
*
* @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a>
* @since 2.20.1
- * @see ProcessChecker
- * @deprecated Use {@code ProcessHandleChecker} via {@link
ProcessChecker#of(String)} instead
*/
-@Deprecated
-final class PpidChecker implements ProcessChecker {
+final class PpidChecker {
private static final long MINUTES_TO_MILLIS = 60L * 1000L;
// 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;
@@ -102,8 +95,7 @@ final class PpidChecker implements ProcessChecker {
this.ppid = ppid;
}
- @Override
- public boolean canUse() {
+ boolean canUse() {
if (isStopped()) {
return false;
}
@@ -119,8 +111,7 @@ public boolean canUse() {
* or this object has been {@link
#destroyActiveCommands() destroyed}
* @throws NullPointerException if extracted e-time is null
*/
- @Override
- public boolean isProcessAlive() {
+ boolean isProcessAlive() {
if (!canUse()) {
throw new IllegalStateException("irrelevant to call
isProcessAlive()");
}
@@ -235,16 +226,14 @@ ProcessInfo consumeLine(String line, ProcessInfo
previousProcessInfo) throws Exc
return reader.execute(psPath + "powershell", "-NoProfile",
"-NonInteractive", "-Command", psCommand);
}
- @Override
- public void destroyActiveCommands() {
+ void destroyActiveCommands() {
stopped = true;
for (Process p = destroyableCommands.poll(); p != null; p =
destroyableCommands.poll()) {
p.destroy();
}
}
- @Override
- public boolean isStopped() {
+ boolean isStopped() {
return stopped;
}
@@ -336,16 +325,10 @@ private static SimpleDateFormat
createWindowsCreationDateFormat() {
return formatter;
}
- @Override
public void stop() {
stopped = true;
}
- @Override
- public ProcessInfo processInfo() {
- return parentProcessInfo;
- }
-
/**
* Reads standard output from {@link Process}.
* <br>
diff --git
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessChecker.java
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessChecker.java
deleted file mode 100644
index ef495eb53..000000000
---
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessChecker.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.
- */
-package org.apache.maven.surefire.booter;
-
-/**
- * Interface for checking if a process (typically the parent Maven plugin) is
still alive.
- * <p>
- * Implementations allow the forked JVM to detect when its parent Maven process
- * has terminated, enabling cleanup and preventing orphaned processes.
- *
- * @since 3.5.5
- */
-public interface ProcessChecker {
-
- /**
- * Creates the appropriate {@link ProcessChecker} implementation for the
given parent PID.
- * <p>
- * On Java 9+, uses {@code ProcessHandleChecker} which leverages the
{@code ProcessHandle} API.
- * On Java 8, falls back to {@link PpidChecker} which uses native commands.
- *
- * @param ppid the parent process ID as a string, or {@code null}
- * @return a new checker instance, or {@code null} if ppid is {@code null}
- */
- static ProcessChecker of(String ppid) {
- if (ppid == null) {
- return null;
- }
- if (ProcessHandleChecker.isAvailable()) {
- return new ProcessHandleChecker(ppid);
- }
- return new PpidChecker(ppid);
- }
-
- /**
- * Returns whether the ProcessHandle API is available in the current JVM.
- *
- * @return {@code true} if running on Java 9+ with ProcessHandle available
- */
- static boolean isProcessHandleSupported() {
- return ProcessHandleChecker.isAvailable();
- }
-
- /**
- * Checks whether this checker can be used to monitor the process.
- * <p>
- * This method must return {@code true} before {@link #isProcessAlive()}
can be called.
- * @deprecated with using ProcessHandleChecker on Java 9+, this method
will always return {@code true} and can be removed in a future release.
- * @return {@code true} if the checker is operational and can monitor the
process
- */
- @Deprecated
- boolean canUse();
-
- /**
- * Checks if the process is still alive.
- * <p>
- * This method can only be called after {@link #canUse()} has returned
{@code true}.
- *
- * @return {@code true} if the process is still running; {@code false} if
it has terminated
- * or if the PID has been reused by a different process
- * @throws IllegalStateException if {@link #canUse()} returns {@code
false} or if the checker
- * has been stopped
- */
- boolean isProcessAlive();
-
- /**
- * Stops the checker and releases any resources.
- * <p>
- * After calling this method, {@link #canUse()} will return {@code false}.
- */
- void stop();
-
- /**
- * Destroys any active commands or subprocesses used by this checker.
- * <p>
- * This is called during shutdown to ensure clean termination.
- */
- void destroyActiveCommands();
-
- /**
- * Checks if the checker has been stopped.
- *
- * @return {@code true} if {@link #stop()} or {@link
#destroyActiveCommands()} has been called
- */
- boolean isStopped();
-
- /**
- * Returns information about the process being checked.
- *
- * @return the process information, or {@code null} if not yet initialized
- */
- ProcessInfo processInfo();
-}
diff --git
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessHandleChecker.java
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessHandleChecker.java
deleted file mode 100644
index a97e6ace4..000000000
---
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/ProcessHandleChecker.java
+++ /dev/null
@@ -1,238 +0,0 @@
-/*
- * 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.
- */
-package org.apache.maven.surefire.booter;
-
-import javax.annotation.Nonnull;
-
-import java.lang.reflect.Method;
-import java.util.Optional;
-
-import static
org.apache.maven.surefire.api.util.ReflectionUtils.invokeMethodWithArray;
-import static org.apache.maven.surefire.api.util.ReflectionUtils.tryGetMethod;
-import static org.apache.maven.surefire.api.util.ReflectionUtils.tryLoadClass;
-
-/**
- * Checks if a process is alive using the ProcessHandle API via reflection.
- * <p>
- * This implementation uses reflection to access the Java 9+ {@code
ProcessHandle} API,
- * allowing the class to compile on Java 8 while functioning on Java 9+.
- * <p>
- * The checker detects two scenarios indicating the process is no longer
available:
- * <ol>
- * <li>The process has terminated ({@code ProcessHandle.isAlive()} returns
{@code false})</li>
- * <li>The PID has been reused by the OS for a new process (start time
differs from initial)</li>
- * </ol>
- *
- * @since 3.5.5
- */
-final class ProcessHandleChecker implements ProcessChecker {
-
- /** Whether ProcessHandle API is available and reflection setup succeeded
*/
- private static final boolean AVAILABLE;
-
- // Method references for ProcessHandle
- private static final Method PROCESS_HANDLE_OF; // ProcessHandle.of(long)
-> Optional<ProcessHandle>
- private static final Method PROCESS_HANDLE_IS_ALIVE; //
ProcessHandle.isAlive() -> boolean
- private static final Method PROCESS_HANDLE_INFO; // ProcessHandle.info()
-> ProcessHandle.Info
-
- // Method references for ProcessHandle.Info
- private static final Method INFO_START_INSTANT; //
ProcessHandle.Info.startInstant() -> Optional<Instant>
-
- // Method reference for Instant
- private static final Method INSTANT_TO_EPOCH_MILLI; //
Instant.toEpochMilli() -> long
-
- static {
- ClassLoader classLoader =
Thread.currentThread().getContextClassLoader();
-
- // Load classes using ReflectionUtils
- Class<?> processHandleClass = tryLoadClass(classLoader,
"java.lang.ProcessHandle");
- Class<?> processHandleInfoClass = tryLoadClass(classLoader,
"java.lang.ProcessHandle$Info");
- Class<?> optionalClass = tryLoadClass(classLoader,
"java.util.Optional");
- Class<?> instantClass = tryLoadClass(classLoader, "java.time.Instant");
-
- Method processHandleOf = null;
- Method processHandleIsAlive = null;
- Method processHandleInfo = null;
- Method infoStartInstant = null;
- Method optionalIsPresent = null;
- Method optionalGet = null;
- Method optionalOrElse = null;
- Method instantToEpochMilli = null;
-
- if (processHandleClass != null && processHandleInfoClass != null &&
optionalClass != null) {
- // ProcessHandle methods
- processHandleOf = tryGetMethod(processHandleClass, "of",
long.class);
- processHandleIsAlive = tryGetMethod(processHandleClass, "isAlive");
- processHandleInfo = tryGetMethod(processHandleClass, "info");
-
- // ProcessHandle.Info methods
- infoStartInstant = tryGetMethod(processHandleInfoClass,
"startInstant");
-
- // Optional methods
- optionalIsPresent = tryGetMethod(optionalClass, "isPresent");
- optionalGet = tryGetMethod(optionalClass, "get");
- optionalOrElse = tryGetMethod(optionalClass, "orElse",
Object.class);
-
- // Instant methods (for processInfo)
- if (instantClass != null) {
- instantToEpochMilli = tryGetMethod(instantClass,
"toEpochMilli");
- }
- }
-
- // All methods must be available for ProcessHandle API to be usable
- AVAILABLE = processHandleOf != null
- && processHandleIsAlive != null
- && processHandleInfo != null
- && infoStartInstant != null
- && optionalIsPresent != null
- && optionalGet != null
- && optionalOrElse != null;
-
- PROCESS_HANDLE_OF = processHandleOf;
- PROCESS_HANDLE_IS_ALIVE = processHandleIsAlive;
- PROCESS_HANDLE_INFO = processHandleInfo;
- INFO_START_INSTANT = infoStartInstant;
- INSTANT_TO_EPOCH_MILLI = instantToEpochMilli;
- }
-
- private final long pid;
- private final Object processHandle; // ProcessHandle (stored as Object)
- private volatile Object initialStartInstant; // Instant (stored as Object)
- private volatile boolean stopped;
-
- /**
- * Creates a new checker for the given process ID.
- *
- * @param pid the process ID as a string
- * @throws NumberFormatException if pid is not a valid long
- */
- ProcessHandleChecker(@Nonnull String pid) {
- this.pid = Long.parseLong(pid);
- try {
- Optional<?> optionalObject = (Optional<?>)
PROCESS_HANDLE_OF.invoke(null, this.pid);
- processHandle = optionalObject.orElse(null);
- initialStartInstant = getInitialStartInstant();
- } catch (Exception e) {
- throw new IllegalStateException("Failed to initialize
ProcessHandleChecker for PID " + pid, e);
- }
- }
-
- /**
- * Returns whether the ProcessHandle API is available for use.
- * This is a static check that can be used by the factory.
- *
- * @return true if ProcessHandle API is available (Java 9+)
- */
- static boolean isAvailable() {
- return AVAILABLE;
- }
-
- @Override
- public boolean canUse() {
- return (AVAILABLE && !stopped);
- }
-
- /**
- * {@inheritDoc}
- * <p>
- * This implementation checks both that the process is alive and that it's
the same process
- * that was originally identified (by comparing start times to detect PID
reuse).
- */
- @Override
- public boolean isProcessAlive() {
- if (!canUse()) {
- throw new IllegalStateException("irrelevant to call
isProcessAlive()");
- }
-
- try {
- // Check if process is still running: processHandle.isAlive()
- boolean isAlive = invokeMethodWithArray(processHandle,
PROCESS_HANDLE_IS_ALIVE);
- if (!isAlive) {
- return false;
- }
-
- // Verify it's the same process (not a reused PID)
- if (initialStartInstant != null) {
- // processHandle.info().startInstant()
- Object info = invokeMethodWithArray(processHandle,
PROCESS_HANDLE_INFO);
- Optional<?> optionalInstant = invokeMethodWithArray(info,
INFO_START_INSTANT);
-
- if (optionalInstant.isPresent()) {
- Object currentStartInstant = optionalInstant.get();
- // PID was reused for a different process
- return currentStartInstant.equals(initialStartInstant);
- }
- }
-
- return true;
- } catch (RuntimeException e) {
- // Reflection failed during runtime - treat as process not alive
- return false;
- }
- }
-
- private Object getInitialStartInstant() {
- try {
- Object info = invokeMethodWithArray(processHandle,
PROCESS_HANDLE_INFO);
- Optional<?> optionalInstant = invokeMethodWithArray(info,
INFO_START_INSTANT);
- return optionalInstant.orElse(null);
- } catch (RuntimeException e) {
- return null;
- }
- }
-
- @Override
- public void destroyActiveCommands() {
- stopped = true;
- // No subprocess to destroy - ProcessHandle doesn't spawn processes
- }
-
- @Override
- public boolean isStopped() {
- return stopped;
- }
-
- @Override
- public void stop() {
- stopped = true;
- }
-
- @Override
- public ProcessInfo processInfo() {
- Object startInstant = getInitialStartInstant();
- if (startInstant == null || INSTANT_TO_EPOCH_MILLI == null) {
- return null;
- }
- try {
- long startTimeMillis = invokeMethodWithArray(startInstant,
INSTANT_TO_EPOCH_MILLI);
- return ProcessInfo.processHandleInfo(String.valueOf(pid),
startTimeMillis);
- } catch (RuntimeException e) {
- return null;
- }
- }
-
- @Override
- public String toString() {
- String args = "pid=" + pid + ", stopped=" + stopped + ", hasHandle=" +
(processHandle != null);
- if (initialStartInstant != null) {
- args += ", startInstant=" + initialStartInstant;
- }
- return "ProcessHandleChecker{" + args + "}";
- }
-}
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 630c7dd5b..771445965 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
@@ -48,17 +48,6 @@ final class ProcessInfo {
return new ProcessInfo(pid, startTimestamp);
}
- /**
- * Creates process info from ProcessHandle API data.
- *
- * @param pid the process ID
- * @param startTimeMillis the process start time in epoch milliseconds
- * @return a new ProcessInfo instance
- */
- static @Nonnull ProcessInfo processHandleInfo(String pid, long
startTimeMillis) {
- return new ProcessInfo(pid, startTimeMillis);
- }
-
private final String pid;
private final long time;
diff --git
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
index 35f5cf75e..3f7b4aa5b 100644
---
a/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
+++
b/surefire-booter/src/main/java/org/apache/maven/surefire/booter/SystemUtils.java
@@ -172,7 +172,6 @@ public static ClassLoader platformClassLoader() {
return null;
}
- // TODO simplify or remove when Java 8 support is dropped
public static Long pid() {
if (isBuiltInJava9AtLeast()) {
Long pid = pidOnJava9();
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 000000000..5b05d1a46
--- /dev/null
+++
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/PpidCheckerTest.java
@@ -0,0 +1,432 @@
+/*
+ * 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.
+ */
+package org.apache.maven.surefire.booter;
+
+import javax.annotation.Nonnull;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.lang.management.ManagementFactory;
+import java.util.Random;
+import java.util.regex.Matcher;
+
+import org.apache.maven.surefire.api.booter.DumpErrorSingleton;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import static java.nio.charset.StandardCharsets.US_ASCII;
+import static java.nio.file.Files.readAllBytes;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.maven.surefire.booter.ProcessInfo.unixProcessInfo;
+import static org.apache.maven.surefire.booter.ProcessInfo.windowsProcessInfo;
+import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_UNIX;
+import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_WINDOWS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeThat;
+import static org.junit.Assume.assumeTrue;
+import static org.powermock.reflect.Whitebox.invokeMethod;
+import static org.powermock.reflect.Whitebox.setInternalState;
+
+/**
+ * Testing {@link PpidChecker} on a platform.
+ *
+ * @author <a href="mailto:[email protected]">Tibor Digana (tibor17)</a>
+ * @since 2.20.1
+ */
+@SuppressWarnings("checkstyle:magicnumber")
+public class PpidCheckerTest {
+ private static final Random RND = new Random();
+
+ @Rule
+ public final ExpectedException exceptions = ExpectedException.none();
+
+ @Rule
+ public final TemporaryFolder tempFolder = new TemporaryFolder();
+
+ private File reportsDir;
+ private String dumpFileName;
+
+ @Before
+ public void initTmpFile() {
+ reportsDir = tempFolder.getRoot();
+ dumpFileName = "surefire-" + RND.nextLong();
+ }
+
+ @After
+ public void deleteTmpFiles() {
+ tempFolder.delete();
+ }
+
+ @Test
+ public void canExecuteUnixPs() {
+ assumeTrue(IS_OS_UNIX);
+ assertThat(PpidChecker.canExecuteUnixPs())
+ .as("Surefire should be tested on real box OS, e.g. Ubuntu or
FreeBSD.")
+ .isTrue();
+ }
+
+ @Test
+ public void shouldHavePidAtBegin() {
+ String expectedPid =
+
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
+
+ PpidChecker checker = new PpidChecker(expectedPid);
+ ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() :
checker.windows();
+
+ assertThat(processInfo).isNotNull();
+
+ assertThat(checker.canUse()).isTrue();
+
+ assertThat(checker.isProcessAlive()).isTrue();
+
+ assertThat(processInfo.getPID()).isEqualTo(expectedPid);
+
+ assertThat(processInfo.getTime()).isGreaterThan(0L);
+ }
+
+ @Test
+ public void shouldHavePid() throws Exception {
+ String expectedPid =
+
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
+
+ PpidChecker checker = new PpidChecker(expectedPid);
+ setInternalState(
+ checker,
+ "parentProcessInfo",
+ IS_OS_UNIX
+ ? unixProcessInfo(expectedPid, 0L)
+ : windowsProcessInfo(expectedPid,
windowsProcessStartTime(checker)));
+
+ // the etime in Unix is measured in seconds. So let's wait 1 s at
least.
+ SECONDS.sleep(1L);
+
+ ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() :
checker.windows();
+
+ assertThat(processInfo).isNotNull();
+
+ assertThat(checker.canUse()).isTrue();
+
+ assertThat(checker.isProcessAlive()).isTrue();
+
+ assertThat(processInfo.getPID()).isEqualTo(expectedPid);
+
+ assertThat(processInfo.getTime()).isGreaterThan(0L);
+
+ assertThat(checker.toString())
+ .contains("ppid=" + expectedPid)
+ .contains("stopped=false")
+ .contains("invalid=false")
+ .contains("error=false");
+
+ checker.destroyActiveCommands();
+ assertThat(checker.canUse()).isFalse();
+ assertThat((boolean) invokeMethod(checker, "isStopped")).isTrue();
+ }
+
+ @Test
+ public void shouldBeStopped() {
+ PpidChecker checker = new PpidChecker("0");
+ checker.stop();
+
+ assertThat(checker.canUse()).isFalse();
+
+ exceptions.expect(IllegalStateException.class);
+ exceptions.expectMessage("irrelevant to call isProcessAlive()");
+
+ checker.isProcessAlive();
+
+ fail("this test should throw exception");
+ }
+
+ @Test
+ public void shouldBeStoppedCheckerWithError() throws Exception {
+ String expectedPid =
+
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
+ DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
+
+ PpidChecker checker = new PpidChecker(expectedPid);
+ checker.stop();
+
+ ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() :
checker.windows();
+ assertThat(processInfo.isError()).isTrue();
+
+ String error = new String(readAllBytes(new File(reportsDir,
dumpFileName + ".dump").toPath()));
+
+ assertThat(error).contains("<<exit>> <<0>>").contains("<<stopped>>
<<true>>");
+ }
+
+ @Test
+ public void shouldBeEmptyDump() throws Exception {
+ String expectedPid =
+
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
+ DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
+
+ PpidChecker checker = new PpidChecker(expectedPid);
+
+ try {
+ Thread.currentThread().interrupt();
+
+ ProcessInfo processInfo = IS_OS_UNIX ? checker.unix() :
checker.windows();
+ //noinspection ResultOfMethodCallIgnored
+ Thread.interrupted();
+ assertThat(processInfo.isError()).isTrue();
+
+ File dumpFile = new File(reportsDir, dumpFileName + ".dump");
+ if (dumpFile.exists()) {
+ String error = new String(readAllBytes(dumpFile.toPath()));
+
+ assertThat(error).contains("<<exit>>").contains("<<stopped>>
<<false>>");
+ }
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ Thread.interrupted();
+ }
+ }
+
+ @Test
+ public void shouldStartedProcessThrowInterruptedException() throws
Exception {
+ String expectedPid =
+
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
+ DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
+
+ PpidChecker checker = new PpidChecker(expectedPid);
+
+ PpidChecker.ProcessInfoConsumer consumer = checker.new
ProcessInfoConsumer(US_ASCII.name()) {
+ @Nonnull
+ @Override
+ ProcessInfo consumeLine(String line, ProcessInfo
previousProcessInfo) throws Exception {
+ throw new InterruptedException();
+ }
+ };
+
+ String[] cmd =
+ IS_OS_WINDOWS ? new String[] {"CMD", "/A", "/X", "/C", "dir"}
: new String[] {"/bin/sh", "-c", "ls"};
+
+ assertThat(consumer.execute(cmd).isError()).isTrue();
+ assertThat(new File(reportsDir, dumpFileName +
".dump")).doesNotExist();
+ }
+
+ @Test
+ public void shouldStartedProcessThrowInterruptedIOException() throws
Exception {
+ String expectedPid =
+
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
+ DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
+
+ PpidChecker checker = new PpidChecker(expectedPid);
+
+ PpidChecker.ProcessInfoConsumer consumer = checker.new
ProcessInfoConsumer(US_ASCII.name()) {
+ @Nonnull
+ @Override
+ ProcessInfo consumeLine(String line, ProcessInfo
previousProcessInfo) throws Exception {
+ throw new InterruptedIOException();
+ }
+ };
+
+ String[] cmd =
+ IS_OS_WINDOWS ? new String[] {"CMD", "/A", "/X", "/C", "dir"}
: new String[] {"/bin/sh", "-c", "ls"};
+
+ assertThat(consumer.execute(cmd).isError()).isTrue();
+ assertThat(new File(reportsDir, dumpFileName +
".dump")).doesNotExist();
+ }
+
+ @Test
+ public void shouldStartedProcessThrowIOException() throws Exception {
+ String expectedPid =
+
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
+ DumpErrorSingleton.getSingleton().init(reportsDir, dumpFileName);
+
+ PpidChecker checker = new PpidChecker(expectedPid);
+
+ PpidChecker.ProcessInfoConsumer consumer = checker.new
ProcessInfoConsumer(US_ASCII.name()) {
+ @Nonnull
+ @Override
+ ProcessInfo consumeLine(String line, ProcessInfo
previousProcessInfo) throws Exception {
+ throw new IOException("wrong command");
+ }
+ };
+
+ String[] cmd =
+ IS_OS_WINDOWS ? new String[] {"CMD", "/A", "/X", "/C", "dir"}
: new String[] {"/bin/sh", "-c", "ls"};
+
+ assertThat(consumer.execute(cmd).isError()).isTrue();
+
+ File dumpFile = new File(reportsDir, dumpFileName + ".dump");
+
+ String error = new String(readAllBytes(dumpFile.toPath()));
+
+
assertThat(error).contains(IOException.class.getName()).contains("wrong
command");
+ }
+
+ @Test
+ public void shouldNotFindSuchPID() {
+ PpidChecker checker = new PpidChecker("1000000");
+ setInternalState(checker, "parentProcessInfo",
ProcessInfo.ERR_PROCESS_INFO);
+
+ assertThat(checker.canUse()).isFalse();
+
+ exceptions.expect(IllegalStateException.class);
+ exceptions.expectMessage("irrelevant to call isProcessAlive()");
+
+ checker.isProcessAlive();
+
+ fail("this test should throw exception");
+ }
+
+ @Test
+ public void shouldNotBeAlive() {
+ PpidChecker checker = new PpidChecker("1000000");
+
+ assertThat(checker.canUse()).isTrue();
+
+ assertThat(checker.isProcessAlive()).isFalse();
+ }
+
+ @Test
+ public void shouldParseEtime() {
+ Matcher m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("38 1234567890");
+ assertThat(m.matches()).isFalse();
+
+ m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("05:38 1234567890");
+ assertThat(m.matches()).isTrue();
+ assertThat(PpidChecker.fromDays(m)).isEqualTo(0L);
+ assertThat(PpidChecker.fromHours(m)).isEqualTo(0L);
+ assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
+ assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
+ assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
+
+ m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("00:05:38 1234567890");
+ assertThat(m.matches()).isTrue();
+ assertThat(PpidChecker.fromDays(m)).isEqualTo(0L);
+ assertThat(PpidChecker.fromHours(m)).isEqualTo(0L);
+ assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
+ assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
+ assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
+
+ m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("01:05:38 1234567890");
+ assertThat(m.matches()).isTrue();
+ assertThat(PpidChecker.fromDays(m)).isEqualTo(0L);
+ assertThat(PpidChecker.fromHours(m)).isEqualTo(3600L);
+ assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
+ assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
+ assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
+
+ m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("02-01:05:38 1234567890");
+ assertThat(m.matches()).isTrue();
+ assertThat(PpidChecker.fromDays(m)).isEqualTo(2 * 24 * 3600L);
+ assertThat(PpidChecker.fromHours(m)).isEqualTo(3600L);
+ assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
+ assertThat(PpidChecker.fromSeconds(m)).isEqualTo(38L);
+ assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
+
+ m = PpidChecker.UNIX_CMD_OUT_PATTERN.matcher("02-1:5:3 1234567890");
+ assertThat(m.matches()).isTrue();
+ assertThat(PpidChecker.fromDays(m)).isEqualTo(2 * 24 * 3600L);
+ assertThat(PpidChecker.fromHours(m)).isEqualTo(3600L);
+ assertThat(PpidChecker.fromMinutes(m)).isEqualTo(300L);
+ assertThat(PpidChecker.fromSeconds(m)).isEqualTo(3L);
+ assertThat(PpidChecker.fromPID(m)).isEqualTo("1234567890");
+ }
+
+ @Test
+ public void shouldParseBusyboxHoursEtime() {
+ Matcher m = PpidChecker.BUSYBOX_CMD_OUT_PATTERN.matcher("38
1234567890");
+ assertThat(m.matches()).isFalse();
+
+ m = PpidChecker.BUSYBOX_CMD_OUT_PATTERN.matcher("05h38 1234567890");
+ assertThat(m.matches()).isTrue();
+ assertThat(PpidChecker.fromBusyboxHours(m)).isEqualTo(3600 * 5L);
+ assertThat(PpidChecker.fromBusyboxMinutes(m)).isEqualTo(60 * 38L);
+ assertThat(PpidChecker.fromBusyboxPID(m)).isEqualTo("1234567890");
+ }
+
+ @Test
+ public void shouldHaveSystemPathToPowerShellOnWindows() throws Exception {
+ assumeTrue(IS_OS_WINDOWS);
+ assumeThat(System.getenv("SystemRoot"), is(notNullValue()));
+ assumeThat(System.getenv("SystemRoot"), is(not("")));
+ assumeTrue(new File(System.getenv("SystemRoot"),
"System32\\WindowsPowerShell\\v1.0").isDirectory());
+ assumeTrue(new File(System.getenv("SystemRoot"),
"System32\\WindowsPowerShell\\v1.0\\powershell.exe").isFile());
+ assertThat((Boolean) invokeMethod(PpidChecker.class,
"hasPowerShellStandardSystemPath"))
+ .isTrue();
+ assertThat(new File(System.getenv("SystemRoot"),
"System32\\WindowsPowerShell\\v1.0\\powershell.exe"))
+ .isFile();
+ }
+
+ @Test
+ public void shouldBeTypeNull() {
+ assertThat(ProcessCheckerType.toEnum(null)).isNull();
+
+ assertThat(ProcessCheckerType.toEnum(" ")).isNull();
+
+ assertThat(ProcessCheckerType.isValid(null)).isTrue();
+ }
+
+ @Test
+ public void shouldBeException() {
+ exceptions.expect(IllegalArgumentException.class);
+ exceptions.expectMessage("unknown process checker");
+
+ assertThat(ProcessCheckerType.toEnum("anything else")).isNull();
+ }
+
+ @Test
+ public void shouldNotBeValid() {
+ assertThat(ProcessCheckerType.isValid("anything")).isFalse();
+ }
+
+ @Test
+ public void shouldBeTypePing() {
+
assertThat(ProcessCheckerType.toEnum("ping")).isEqualTo(ProcessCheckerType.PING);
+
+ assertThat(ProcessCheckerType.isValid("ping")).isTrue();
+
+ assertThat(ProcessCheckerType.PING.getType()).isEqualTo("ping");
+ }
+
+ @Test
+ public void shouldBeTypeNative() {
+
assertThat(ProcessCheckerType.toEnum("native")).isEqualTo(ProcessCheckerType.NATIVE);
+
+ assertThat(ProcessCheckerType.isValid("native")).isTrue();
+
+ assertThat(ProcessCheckerType.NATIVE.getType()).isEqualTo("native");
+ }
+
+ @Test
+ public void shouldBeTypeAll() {
+
assertThat(ProcessCheckerType.toEnum("all")).isEqualTo(ProcessCheckerType.ALL);
+
+ assertThat(ProcessCheckerType.isValid("all")).isTrue();
+
+ assertThat(ProcessCheckerType.ALL.getType()).isEqualTo("all");
+ }
+
+ private static long windowsProcessStartTime(PpidChecker checker) {
+ return checker.windows().getTime();
+ }
+}
diff --git
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ProcessCheckerTest.java
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ProcessCheckerTest.java
deleted file mode 100644
index ac5883d17..000000000
---
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ProcessCheckerTest.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * 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.
- */
-package org.apache.maven.surefire.booter;
-
-import java.lang.management.ManagementFactory;
-
-import org.junit.Assume;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.fail;
-
-/**
- * Testing {@link ProcessChecker} via {@link ProcessChecker#of(String)}.
- *
- * @since 2.20.1
- */
-@SuppressWarnings("checkstyle:magicnumber")
-public class ProcessCheckerTest {
-
- @Rule
- public final ExpectedException exceptions = ExpectedException.none();
-
- @Test
- public void shouldHavePidAtBegin() {
- String expectedPid =
-
ManagementFactory.getRuntimeMXBean().getName().split("@")[0].trim();
-
- ProcessChecker checker = ProcessChecker.of(expectedPid);
-
- assertThat(checker).isNotNull();
-
- assertThat(checker.canUse()).isTrue();
-
- assertThat(checker.isProcessAlive()).isTrue();
-
- ProcessInfo processInfo = checker.processInfo();
- assertThat(processInfo).isNotNull();
- assertThat(processInfo.getPID()).isEqualTo(expectedPid);
- assertThat(processInfo.getTime()).isGreaterThan(0L);
- }
-
- @Test
- public void shouldBeStopped() {
- ProcessChecker checker = ProcessChecker.of("0");
- checker.stop();
-
- assertThat(checker.canUse()).isFalse();
-
- exceptions.expect(IllegalStateException.class);
- exceptions.expectMessage("irrelevant to call isProcessAlive()");
-
- checker.isProcessAlive();
-
- fail("this test should throw exception");
- }
-
- @Test
- public void exceptionCallIsProcessAlive() {
- // FIXME DisabledOnJre when we migrate to junit5 and run on unix too
- // winddows java 8 must depends on wwmc something available
- double v =
Double.parseDouble(System.getProperty("java.specification.version"));
- Assume.assumeTrue(v >= 9.0);
- ProcessChecker checker =
ProcessChecker.of(Long.toString(Integer.MAX_VALUE));
- checker.stop();
-
assertThatThrownBy(checker::isProcessAlive).isInstanceOf(IllegalStateException.class);
- }
-
- @Test
- public void shouldReturnNullForNullPpid() {
- ProcessChecker checker = ProcessChecker.of(null);
- assertThat(checker).isNull();
- }
-
- @Test
- public void shouldBeTypeNull() {
- assertThat(ProcessCheckerType.toEnum(null)).isNull();
-
- assertThat(ProcessCheckerType.toEnum(" ")).isNull();
-
- assertThat(ProcessCheckerType.isValid(null)).isTrue();
- }
-
- @Test
- public void shouldBeException() {
- exceptions.expect(IllegalArgumentException.class);
- exceptions.expectMessage("unknown process checker");
-
- assertThat(ProcessCheckerType.toEnum("anything else")).isNull();
- }
-
- @Test
- public void shouldNotBeValid() {
- assertThat(ProcessCheckerType.isValid("anything")).isFalse();
- }
-
- @Test
- public void shouldBeTypePing() {
-
assertThat(ProcessCheckerType.toEnum("ping")).isEqualTo(ProcessCheckerType.PING);
-
- assertThat(ProcessCheckerType.isValid("ping")).isTrue();
-
- assertThat(ProcessCheckerType.PING.getType()).isEqualTo("ping");
- }
-
- @Test
- public void shouldBeTypeNative() {
-
assertThat(ProcessCheckerType.toEnum("native")).isEqualTo(ProcessCheckerType.NATIVE);
-
- assertThat(ProcessCheckerType.isValid("native")).isTrue();
-
- assertThat(ProcessCheckerType.NATIVE.getType()).isEqualTo("native");
- }
-
- @Test
- public void shouldBeTypeAll() {
-
assertThat(ProcessCheckerType.toEnum("all")).isEqualTo(ProcessCheckerType.ALL);
-
- assertThat(ProcessCheckerType.isValid("all")).isTrue();
-
- assertThat(ProcessCheckerType.ALL.getType()).isEqualTo("all");
- }
-
- @Test
- public void shouldCreateCheckerForCurrentProcess() {
- // Get current process PID using reflection to stay Java 8 compatible
- String currentPid = getCurrentPid();
- if (currentPid == null) {
- // Skip test if we can't get PID
- return;
- }
-
- ProcessChecker checker = ProcessChecker.of(currentPid);
-
- assertThat(checker).isNotNull();
- assertThat(checker.canUse()).isTrue();
- assertThat(checker.isProcessAlive()).isTrue();
- assertThat(checker.isStopped()).isFalse();
- }
-
- @Test
- public void shouldSelectProcessHandleCheckerOnJava9Plus() {
- if (!ProcessChecker.isProcessHandleSupported()) {
- // Skip test if ProcessHandle is not available (Java 8)
- return;
- }
-
- String currentPid = getCurrentPid();
- if (currentPid == null) {
- return;
- }
-
- ProcessChecker checker = ProcessChecker.of(currentPid);
-
assertThat(checker.getClass().getSimpleName()).isEqualTo("ProcessHandleChecker");
- }
-
- @Test
- public void shouldStopChecker() {
- String currentPid = getCurrentPid();
- if (currentPid == null) {
- return;
- }
-
- ProcessChecker checker = ProcessChecker.of(currentPid);
-
- assertThat(checker.canUse()).isTrue();
- assertThat(checker.isStopped()).isFalse();
-
- checker.stop();
-
- assertThat(checker.isStopped()).isTrue();
- assertThat(checker.canUse()).isFalse();
- }
-
- @Test
- public void shouldDestroyActiveCommands() {
- String currentPid = getCurrentPid();
- if (currentPid == null) {
- return;
- }
-
- ProcessChecker checker = ProcessChecker.of(currentPid);
- assertThat(checker.canUse()).isTrue();
-
- checker.destroyActiveCommands();
-
- assertThat(checker.isStopped()).isTrue();
- assertThat(checker.canUse()).isFalse();
- }
-
- @Test
- public void shouldHandleNonExistentProcess() {
- // Use an invalid PID that's unlikely to exist
- ProcessChecker checker =
ProcessChecker.of(Long.toString(Long.MAX_VALUE));
-
- assertThat(checker).isNotNull();
-
- assertThat(checker.canUse()).isTrue();
-
- assertThat(checker.isProcessAlive()).isFalse();
- }
-
- /**
- * Gets the current process PID in a way that works on both Java 8 and
Java 9+.
- */
- private static String getCurrentPid() {
- // Try ProcessHandle (Java 9+) first via reflection
- try {
- Class<?> processHandleClass =
Class.forName("java.lang.ProcessHandle");
- Object currentHandle =
processHandleClass.getMethod("current").invoke(null);
- Long pid = (Long)
processHandleClass.getMethod("pid").invoke(currentHandle);
- return String.valueOf(pid);
- } catch (Exception e) {
- // Fall back to ManagementFactory (works on Java 8)
- try {
- String name =
java.lang.management.ManagementFactory.getRuntimeMXBean()
- .getName();
- // Format is "pid@hostname"
- int atIndex = name.indexOf('@');
- if (atIndex > 0) {
- return name.substring(0, atIndex);
- }
- } catch (Exception ex) {
- // Ignore
- }
- }
- return null;
- }
-}
diff --git
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ProcessHandleCheckerTest.java
b/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ProcessHandleCheckerTest.java
deleted file mode 100644
index cc60e6122..000000000
---
a/surefire-booter/src/test/java/org/apache/maven/surefire/booter/ProcessHandleCheckerTest.java
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * 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.
- */
-package org.apache.maven.surefire.booter;
-
-import java.lang.management.ManagementFactory;
-
-import org.junit.Assume;
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assume.assumeTrue;
-
-/**
- * Tests for {@link ProcessHandleChecker}.
- * <p>
- * These tests use reflection-based PID detection to work on both Java 8 and
Java 9+.
- */
-public class ProcessHandleCheckerTest {
-
- @Test
- public void shouldReportAvailableOnJava9Plus() {
- // This test runs on modern JVMs, so isAvailable() should return true
- // FIXME DisabledOnJre when we migrate to junit5
- double v =
Double.parseDouble(System.getProperty("java.specification.version"));
- Assume.assumeTrue(v >= 9.0);
- assertThat(ProcessHandleChecker.isAvailable()).isTrue();
- }
-
- @Test
- public void shouldDetectCurrentProcessAsAlive() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- String currentPid = getCurrentPid();
- assumeTrue("Could not determine current PID", currentPid != null);
-
- ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
-
- assertThat(checker.canUse()).isTrue();
- assertThat(checker.isProcessAlive()).isTrue();
- assertThat(checker.isStopped()).isFalse();
- }
-
- @Test
- public void shouldDetectNonExistentProcessAsNotUsable() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- // Use an invalid PID that's unlikely to exist
- ProcessHandleChecker checker = new ProcessHandleChecker("999999999");
-
- assertThat(checker.canUse()).isTrue();
- }
-
- @Test
- public void shouldStopChecker() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- String currentPid = getCurrentPid();
- assumeTrue("Could not determine current PID", currentPid != null);
-
- ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
-
- assertThat(checker.canUse()).isTrue();
- assertThat(checker.isStopped()).isFalse();
-
- checker.stop();
-
- assertThat(checker.isStopped()).isTrue();
- assertThat(checker.canUse()).isFalse();
- }
-
- @Test
- public void shouldDestroyActiveCommands() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- String currentPid = getCurrentPid();
- assumeTrue("Could not determine current PID", currentPid != null);
-
- ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
-
- assertThat(checker.canUse()).isTrue();
-
- checker.destroyActiveCommands();
-
- assertThat(checker.isStopped()).isTrue();
- assertThat(checker.canUse()).isFalse();
- }
-
- @Test
- public void shouldReturnMeaningfulToString() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- String currentPid = getCurrentPid();
- assumeTrue("Could not determine current PID", currentPid != null);
-
- ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
-
- String toString = checker.toString();
-
- assertThat(toString)
- .contains("ProcessHandleChecker")
- .contains("pid=" + currentPid)
- .contains("stopped=false");
- }
-
- @Test
- public void shouldReturnToStringWithStartInstantAfterCanUse() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- String currentPid = getCurrentPid();
- assumeTrue("Could not determine current PID", currentPid != null);
-
- ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
-
- checker.canUse();
- String toString = checker.toString();
-
-
assertThat(toString).contains("ProcessHandleChecker").contains("hasHandle=true");
- }
-
- @Test
- public void shouldCreateViaFactoryMethod() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- String currentPid = getCurrentPid();
- assumeTrue("Could not determine current PID", currentPid != null);
-
- ProcessChecker checker = ProcessChecker.of(currentPid);
-
- assertThat(checker).isInstanceOf(ProcessHandleChecker.class);
- assertThat(checker.canUse()).isTrue();
- assertThat(checker.isProcessAlive()).isTrue();
- }
-
- @Test
- public void shouldReturnNullFromFactoryForNullPpid() {
- ProcessChecker checker = ProcessChecker.of(null);
-
- assertThat(checker).isNull();
- }
-
- @Test
- public void shouldThrowOnInvalidPpidFormat() {
- assertThatThrownBy(() -> new
ProcessHandleChecker("not-a-number")).isInstanceOf(NumberFormatException.class);
- }
-
- @Test
- public void shouldReturnProcessInfoAfterCanUse() {
- assumeTrue("ProcessHandle not available",
ProcessHandleChecker.isAvailable());
-
- String currentPid = getCurrentPid();
- assumeTrue("Could not determine current PID", currentPid != null);
-
- ProcessHandleChecker checker = new ProcessHandleChecker(currentPid);
-
- // Now processInfo() should return valid info
- ProcessInfo processInfo = checker.processInfo();
- assertThat(processInfo).isNotNull();
- assertThat(processInfo.getPID()).isEqualTo(currentPid);
- assertThat(processInfo.getTime()).isGreaterThan(0L);
- }
-
- /**
- * Gets the current process PID using reflection (Java 8 compatible).
- *
- * @return the current process PID as a string, or null if it cannot be
determined
- */
- private static String getCurrentPid() {
- // Try ProcessHandle.current().pid() via reflection (Java 9+)
- try {
- Class<?> processHandleClass =
Class.forName("java.lang.ProcessHandle");
- Object currentHandle =
processHandleClass.getMethod("current").invoke(null);
- Long pid = (Long)
processHandleClass.getMethod("pid").invoke(currentHandle);
- return String.valueOf(pid);
- } catch (Exception e) {
- // Fall back to ManagementFactory (works on Java 8)
- try {
- String name = ManagementFactory.getRuntimeMXBean().getName();
- int atIndex = name.indexOf('@');
- if (atIndex > 0) {
- return name.substring(0, atIndex);
- }
- } catch (Exception ex) {
- // Ignore
- }
- }
- return null;
- }
-}