org.apache.maven.surefire.booter.SystemUtils Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of surefire-booter Show documentation
Show all versions of surefire-booter Show documentation
API and Facilities used by forked tests running in JVM sub-process.
/*
* 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.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.lang.management.ManagementFactory;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.maven.surefire.api.util.ReflectionUtils;
import static java.lang.Thread.currentThread;
import static java.util.Objects.requireNonNull;
import static org.apache.maven.surefire.api.util.ReflectionUtils.invokeMethodChain;
import static org.apache.maven.surefire.api.util.ReflectionUtils.invokeMethodWithArray;
import static org.apache.maven.surefire.api.util.ReflectionUtils.tryLoadClass;
import static org.apache.maven.surefire.shared.lang3.JavaVersion.JAVA_9;
import static org.apache.maven.surefire.shared.lang3.JavaVersion.JAVA_RECENT;
import static org.apache.maven.surefire.shared.lang3.StringUtils.isNumeric;
import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_FREE_BSD;
import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_LINUX;
import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_NET_BSD;
import static org.apache.maven.surefire.shared.lang3.SystemUtils.IS_OS_OPEN_BSD;
/**
* JDK 9 support.
*
* @author Tibor Digana (tibor17)
* @since 2.20.1
*/
public final class SystemUtils {
private static final BigDecimal JIGSAW_JAVA_VERSION = new BigDecimal(9).stripTrailingZeros();
private static final int PROC_STATUS_PID_FIRST_CHARS = 20;
private SystemUtils() {
throw new IllegalStateException("no instantiable constructor");
}
/**
* @param jvmExecPath e.g. /jdk/bin/java, /jdk/jre/bin/java
* @return {@code true} if {@code jvmExecPath} is path to java binary executor
*/
public static boolean endsWithJavaPath(String jvmExecPath) {
File javaExec = new File(jvmExecPath).getAbsoluteFile();
File bin = javaExec.getParentFile();
String exec = javaExec.getName();
return exec.startsWith("java") && bin != null && bin.getName().equals("bin");
}
/**
* If {@code jvmExecutable} is /jdk/bin/java
(since jdk9) or /jdk/jre/bin/java
* (prior to jdk9) then the absolute path to JDK home is returned /jdk
.
*
* Null is returned if {@code jvmExecutable} is incorrect.
*
* @param jvmExecutable /jdk/bin/java* or /jdk/jre/bin/java*
* @return path to jdk directory; or null
if wrong path or directory layout of JDK installation.
*/
public static File toJdkHomeFromJvmExec(String jvmExecutable) {
File bin = new File(jvmExecutable).getAbsoluteFile().getParentFile();
if ("bin".equals(bin.getName())) {
File parent = bin.getParentFile();
if ("jre".equals(parent.getName())) {
File jdk = parent.getParentFile();
return new File(jdk, "bin").isDirectory() ? jdk : null;
}
return parent;
}
return null;
}
/**
* If system property java.home
is /jdk
(since jdk9) or /jdk/jre
* (prior to jdk9) then the absolute path to
* JDK home is returned /jdk
.
*
* @return path to JDK
*/
public static File toJdkHomeFromJre() {
return toJdkHomeFromJre(System.getProperty("java.home"));
}
/**
* If {@code jreHome} is /jdk
(since jdk9) or /jdk/jre
(prior to jdk9) then
* the absolute path to JDK home is returned /jdk
.
*
* JRE home directory {@code jreHome} must be taken from system property java.home
.
*
* @param jreHome path to /jdk or /jdk/jre
* @return path to JDK
*/
static File toJdkHomeFromJre(String jreHome) {
File pathToJreOrJdk = new File(jreHome).getAbsoluteFile();
return "jre".equals(pathToJreOrJdk.getName()) ? pathToJreOrJdk.getParentFile() : pathToJreOrJdk;
}
public static BigDecimal toJdkVersionFromReleaseFile(File jdkHome) {
File release = new File(requireNonNull(jdkHome).getAbsoluteFile(), "release");
if (!release.isFile()) {
return null;
}
Properties properties = new Properties();
try (InputStream is = new FileInputStream(release)) {
properties.load(is);
String javaVersion = properties.getProperty("JAVA_VERSION").replace("\"", "");
StringTokenizer versions = new StringTokenizer(javaVersion, "._");
if (versions.countTokens() == 1) {
javaVersion = versions.nextToken();
} else if (versions.countTokens() >= 2) {
String majorVersion = versions.nextToken();
String minorVersion = versions.nextToken();
javaVersion = isNumeric(minorVersion) ? majorVersion + "." + minorVersion : majorVersion;
} else {
return null;
}
return new BigDecimal(javaVersion);
} catch (IOException e) {
return null;
}
}
public static boolean isJava9AtLeast(String jvmExecutablePath) {
File externalJavaHome = toJdkHomeFromJvmExec(jvmExecutablePath);
File thisJavaHome = toJdkHomeFromJre();
if (thisJavaHome.equals(externalJavaHome)) {
return isBuiltInJava9AtLeast();
} else {
BigDecimal releaseFileVersion =
externalJavaHome == null ? null : toJdkVersionFromReleaseFile(externalJavaHome);
return isJava9AtLeast(releaseFileVersion);
}
}
public static boolean isBuiltInJava9AtLeast() {
return JAVA_RECENT.atLeast(JAVA_9);
}
public static boolean isJava9AtLeast(BigDecimal version) {
return version != null && version.compareTo(JIGSAW_JAVA_VERSION) >= 0;
}
public static ClassLoader platformClassLoader() {
if (isBuiltInJava9AtLeast()) {
return reflectClassLoader(ClassLoader.class, "getPlatformClassLoader");
}
return null;
}
public static Long pid() {
if (isBuiltInJava9AtLeast()) {
Long pid = pidOnJava9();
if (pid != null) {
return pid;
}
}
if (IS_OS_LINUX) {
try {
return pidStatusOnLinux();
} catch (Exception e) {
// examine PID via JMX
}
} else if (IS_OS_FREE_BSD || IS_OS_NET_BSD || IS_OS_OPEN_BSD) {
try {
return pidStatusOnBSD();
} catch (Exception e) {
// examine PID via JMX
}
}
return pidOnJMX();
}
static Long pidOnJMX() {
String processName = ManagementFactory.getRuntimeMXBean().getName();
if (processName.contains("@")) {
String pid = processName.substring(0, processName.indexOf('@')).trim();
try {
return Long.parseLong(pid);
} catch (NumberFormatException e) {
return null;
}
}
return null;
}
/**
* $ cat /proc/self/stat
*
* 48982 (cat) R 9744 48982 9744 34818 48982 8192 185 0 0 0 0 0 0 0 20 0 1 0
* 137436614 103354368 134 18446744073709551615 4194304 4235780 140737488346592
* 140737488343784 252896458544 0 0 0 0 0 0 0 17 2 0 0 0 0 0
*
* $ SELF_PID=$(cat /proc/self/stat)
*
* $ echo $CPU_ID | gawk '{print $1}'
*
* 48982
*
* @return self PID
* @throws Exception i/o and number format exc
*/
static Long pidStatusOnLinux() throws Exception {
return pidStatusOnLinux("");
}
/**
* For testing purposes only.
*
* @param root shifted to test-classes
* @return same as in {@link #pidStatusOnLinux()}
* @throws Exception same as in {@link #pidStatusOnLinux()}
*/
static Long pidStatusOnLinux(String root) throws Exception {
try (FileReader input = new FileReader(root + "/proc/self/stat")) {
// Reading and encoding 20 characters is bit faster than whole line.
// size of (long) = 19, + 1 space
char[] buffer = new char[PROC_STATUS_PID_FIRST_CHARS];
String startLine = new String(buffer, 0, input.read(buffer));
return Long.parseLong(startLine.substring(0, startLine.indexOf(' ')));
}
}
/**
* The process status. This file is read-only and returns a single
* line containing multiple space-separated fields.
*
* See procfs status
*
* # cat /proc/curproc/status
*
* cat 60424 60386 60424 60386 5,0 ctty 972854153,236415 0,0 0,1043 nochan 0 0 0,0 prisoner
*
* Fields are:
*
* comm pid ppid pgid sid maj, min ctty, sldr start user/system time wmsg euid ruid rgid,egid,
* groups[1 .. NGROUPS] hostname
*
* @return current PID
* @throws Exception if could not read /proc/curproc/status
*/
static Long pidStatusOnBSD() throws Exception {
return pidStatusOnBSD("");
}
/**
* For testing purposes only.
*
* @param root shifted to test-classes
* @return same as in {@link #pidStatusOnBSD()}
* @throws Exception same as in {@link #pidStatusOnBSD()}
*/
static Long pidStatusOnBSD(String root) throws Exception {
try (BufferedReader input = new BufferedReader(new FileReader(root + "/proc/curproc/status"))) {
String line = input.readLine();
int i1 = 1 + line.indexOf(' ');
int i2 = line.indexOf(' ', i1);
return Long.parseLong(line.substring(i1, i2));
}
}
static Long pidOnJava9() {
ClassLoader classLoader = currentThread().getContextClassLoader();
Class> processHandle = tryLoadClass(classLoader, "java.lang.ProcessHandle");
Class>[] classesChain = {processHandle, processHandle};
String[] methodChain = {"current", "pid"};
return invokeMethodChain(classesChain, methodChain, null);
}
static ClassLoader reflectClassLoader(Class> target, String getterMethodName) {
try {
Method getter = ReflectionUtils.getMethod(target, getterMethodName);
return invokeMethodWithArray(null, getter);
} catch (RuntimeException e) {
return null;
}
}
}