Skip to content

Commit

Permalink
Merge pull request #80 from cljohnso/testJava21
Browse files Browse the repository at this point in the history
Enable building & testing with Java 21
  • Loading branch information
chrisdennis authored Feb 22, 2024
2 parents 08d6f4d + bdd96d4 commit 273eab0
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 18 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
image: almalinux:latest
strategy:
matrix:
java: [ 8, 11, 17 ]
java: [ 8, 11, 17, 21 ]
distribution: [ zulu ]
fail-fast: false
max-parallel: 4
Expand Down Expand Up @@ -63,7 +63,7 @@ jobs:
options: --user 1001:1001
strategy:
matrix:
java: [ 8, 11, 17 ]
java: [ 8, 11, 17, 21 ]
distribution: [ zulu ]
fail-fast: false
max-parallel: 4
Expand Down Expand Up @@ -99,7 +99,7 @@ jobs:
strategy:
matrix:
os: [ ubuntu-latest, macOS-latest, windows-latest ]
java: [ 8, 11, 17 ]
java: [ 8, 11, 17, 21 ]
distribution: [ zulu ]
fail-fast: false
max-parallel: 4
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ tasks {
options.encoding = "UTF-8"
options.isDeprecation = true
options.isWarnings = true
options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
// '-Xlint:options' needed to ignore warning about source/target Java 8 obsolescence
options.compilerArgs.addAll(listOf("-Xlint:all", "-Xlint:-options", "-Werror"))
}

withType<Javadoc> {
Expand Down Expand Up @@ -145,6 +146,19 @@ tasks {
}
}

spotbugsMain {
val spotbugsLastCheckLevel = JavaVersion.VERSION_21 // Most current Java version assessed for Spotbugs support
val spotbugsSuppressionLevel = JavaVersion.VERSION_21 // Java version at which Spotbugs is suppressed
if (JavaVersion.current() > spotbugsLastCheckLevel) {
throw GradleException("Current version of Java exceeds Spotbugs suppression level; re-examine Spotbugs suppression", null)
} else if (JavaVersion.current() >= spotbugsSuppressionLevel) {
logger.warn("Suppressing Spotbugs; current Java Version ${JavaVersion.current()} >= Spotbugs support version ${spotbugsSuppressionLevel}")
enabled = false
} else {
enabled = true
}
}

spotbugsTest {
enabled = false
}
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defaultVersion=0.0.19-SNAPSHOT
# gradle release (or: gradle release -Prelease.useAutomaticVersion=true)
version=0.0.19-SNAPSHOT

spotbugsAnnotationsVersion=4.0.2
spotbugsAnnotationsVersion=4.8.3

slf4jBaseVersion=1.7.32
slf4jUpperVersion=1.7.9999
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
14 changes: 7 additions & 7 deletions gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -145,15 +145,15 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
Expand Down Expand Up @@ -202,11 +202,11 @@ fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.

set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package org.terracotta.utilities.test.net;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.utilities.exec.Shell;
Expand Down Expand Up @@ -1599,11 +1600,13 @@ public HostExecutionException(String message, Throwable cause) {
this.result = null;
}

@SuppressFBWarnings("EI_EXPOSE_REP2")
public HostExecutionException(String command, Shell.Result result, Throwable cause) {
super(message(command, result), cause);
this.result = result;
}

@SuppressFBWarnings("EI_EXPOSE_REP2")
public HostExecutionException(String command, Shell.Result result) {
super(message(command, result));
this.result = result;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.management.BufferPoolMXBean;
import java.lang.management.LockInfo;
import java.lang.management.ManagementFactory;
Expand Down Expand Up @@ -88,7 +91,7 @@ public ThreadPair(Thread thread) {
* terminated, its slot in the returned array is null.
*/
List<Thread> threadList = new ArrayList<>(threads);
long[] threadIds = threadList.stream().mapToLong(Thread::getId).toArray();
long[] threadIds = threadList.stream().mapToLong(ThreadId::get).toArray();
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadIds, true, true);
for (int i = 0; i < threadList.size(); i++) {
Expand All @@ -102,7 +105,7 @@ public ThreadPair(Thread thread) {
if (p.threadInfo == null) {
Thread t = p.thread;
printStream.format("\"%s\" Id=%d prio=%d %s%n",
t.getName(), t.getId(), t.getPriority(), t.getState());
t.getName(), ThreadId.get(t), t.getPriority(), t.getState());
for (StackTraceElement element : t.getStackTrace()) {
printStream.format("\tat %s%n", element);
}
Expand Down Expand Up @@ -313,6 +316,17 @@ public static long getLongPid() {
return Pid.PID.orElse(-1);
}

/**
* Gets the thread id of the specified thread. For Java 19+, this method returns the
* value of {@code Thread.threadId}; for Java 18-, this method returns the value of
* {@code Thread.getId}.
* @param thread the {@code thread} for which the id is to be returned
* @return the {@code Thread} id; {@code -1} if an error was raised while obtaining the id
*/
public static long threadId(Thread thread) {
return ThreadId.get(thread);
}

/**
* Returns an array contain a reference to all {@link Thread} instances in the JVM. Since the JVM is
* not stopped, the returned array may miss threads or have no longer extant threads.
Expand Down Expand Up @@ -389,6 +403,52 @@ public static MaxDirectMemoryInfo getMaxDirectMemoryInfo() {
return MaxDirectMemoryInfoHelper.INSTANCE;
}

/**
* Handles using the JVM-appropriate method to obtain the id for a {@code Thread}.
*/
private static class ThreadId {
private static final MethodHandle THREAD_ID_METHOD;

static {
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
MethodType longNiladic = MethodType.methodType(long.class);
MethodHandle threadId;
try {
// Thread.threadId is added in Java 19 as a replacement for Thread.getId
threadId = lookup.findVirtual(Thread.class, "threadId", longNiladic);
} catch (NoSuchMethodException | IllegalAccessException e) {
try {
// Thread.getId is deprecated in Java 19
threadId = lookup.findVirtual(Thread.class, "getId", longNiladic);
} catch (NoSuchMethodException | IllegalAccessException ex) {
threadId = MethodHandles.constant(long.class, -1L);

System.err.format("Failed to create handle for Thread 'id' method; calls to %s.threadId will return -1", Diagnostics.class.getSimpleName());
ex.addSuppressed(e);
ex.printStackTrace(System.err);
}
}
THREAD_ID_METHOD = threadId;
}

/**
* Gets the id of the specified {@code Thread}.
* @param t the {@code Thread} for which the id is to be obtained
* @return the {@code Thread} id; {@code -1} if there is an error obtaining the {@code Thread} id
*/
private static long get(Thread t) {
try {
return (long)THREAD_ID_METHOD.invoke(t);
} catch (Error e) {
throw e;
} catch (Throwable throwable) {
System.err.format("Failed obtain thread id from %s; returning -1", THREAD_ID_METHOD);
throwable.printStackTrace(System.err);
return -1L;
}
}
}

/**
* Determines the process identifier of the current process.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static java.util.stream.Collectors.toList;
import static org.hamcrest.Matchers.allOf;
Expand Down Expand Up @@ -97,6 +99,33 @@ public void testGetPid() {
assertThat(intPid, is((int)pid));
}

@Test
public void testThreadId() throws Exception {
Thread currentThread = Thread.currentThread();
long threadId = Diagnostics.threadId(currentThread);
assertThat(threadId, is(not(-1L)));

int javaVersion = 0;
Matcher matcher = Pattern.compile("(?<first>\\d+)\\.(?<second>\\d+)\\..*").matcher(System.getProperty("java.version"));
if (matcher.matches()) {
if (matcher.group("first").equals("1")) {
javaVersion = Integer.parseInt(matcher.group("second"));
} else {
javaVersion = Integer.parseInt(matcher.group("first"));
}
long id;
if (javaVersion >= 19) {
// At Java 19, Thread.threadId is added and Thread.getId is deprecated
id = (long)Thread.class.getMethod("threadId").invoke(currentThread);
} else {
id = (long)Thread.class.getMethod("getId").invoke(currentThread);
}
assertThat(threadId, is(id));
} else {
fail("Unrecognized 'java.version'=" + System.getProperty("java.version"));
}
}

private List<String> perform(Consumer<PrintStream> task) {
try {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream(4096);
Expand Down
38 changes: 35 additions & 3 deletions tools/src/main/java/org/terracotta/utilities/exec/Shell.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -86,13 +89,16 @@ public static Result execute(Charset consoleEncoding, String... command) throws
/**
* The {@link #execute(Charset, String...)} command execution result.
*/
public static final class Result implements Iterable<String> {
private final List<String> commandLines;
// Java 21 warns of non-Seriazable fields in a Serialization object
public static final class Result implements Iterable<String>, Serializable {
private static final long serialVersionUID = -5555911693879268481L;

private final transient List<String> commandLines;
private final int exitCode;

private Result(int exitCode, List<String> commandLines) {
this.exitCode = exitCode;
this.commandLines = Collections.unmodifiableList(commandLines);
this.commandLines = Collections.unmodifiableList(new ArrayList<>(commandLines));
}

/**
Expand All @@ -119,6 +125,32 @@ public List<String> lines() {
public Iterator<String> iterator() {
return commandLines.iterator();
}

private void readObject(ObjectInputStream s) throws InvalidObjectException {
throw new InvalidObjectException("SerializationProxy expected");
}

private Object writeReplace() {
return new SerializationProxy(this);
}

/**
* Internal proxy for serialization of {@link Result} instances.
*/
private static final class SerializationProxy implements Serializable {
private static final long serialVersionUID = -8686283741098525116L;
private final int exitCode;
private final ArrayList<String> commandLines;

SerializationProxy(Result result) {
this.exitCode = result.exitCode;
this.commandLines = new ArrayList<>(result.commandLines);
}

private Object readResolve() {
return new Result(exitCode, commandLines);
}
}
}

/**
Expand Down

0 comments on commit 273eab0

Please sign in to comment.