From afd9f4c18664a8c725acb790235338a3c6ae93d5 Mon Sep 17 00:00:00 2001 From: Michal Karm Babacek Date: Wed, 18 Dec 2024 00:47:42 +0100 Subject: [PATCH] Tests graalvm-community-jdk21u-issues-28 --- apps/jdkreflections/pom.xml | 39 ++++++++ .../src/main/java/jdkreflections/Main.java | 88 +++++++++++++++++++ pom.xml | 1 + .../tests/integration/AppReproducersTest.java | 57 ++++++++++++ .../graalvm/tests/integration/utils/Apps.java | 12 ++- .../integration/utils/BuildAndRunCmds.java | 28 +++++- .../tests/integration/utils/Commands.java | 2 +- .../integration/utils/ContainerNames.java | 1 + .../integration/utils/WhitelistLogLines.java | 7 ++ 9 files changed, 232 insertions(+), 3 deletions(-) create mode 100644 apps/jdkreflections/pom.xml create mode 100644 apps/jdkreflections/src/main/java/jdkreflections/Main.java diff --git a/apps/jdkreflections/pom.xml b/apps/jdkreflections/pom.xml new file mode 100644 index 0000000..e3709dd --- /dev/null +++ b/apps/jdkreflections/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + jdkreflections + jdkreflections + 1 + + jdkreflections + + + org.graalvm.tests.integration + parent + 1.0.0-SNAPSHOT + ../../pom.xml + + + + 3.2.0 + + + + jdkreflections + + + org.apache.maven.plugins + maven-jar-plugin + ${maven-jar-plugin.version} + + + + jdkreflections.Main + + + + + + + diff --git a/apps/jdkreflections/src/main/java/jdkreflections/Main.java b/apps/jdkreflections/src/main/java/jdkreflections/Main.java new file mode 100644 index 0000000..f05292a --- /dev/null +++ b/apps/jdkreflections/src/main/java/jdkreflections/Main.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024, Red Hat Inc. All rights reserved. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * Licensed 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 jdkreflections; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +public class Main { + + /** + * An intentionally elaborate way to do this to test the reflection support + * on java.lang.Thread for native-image build configuration. + * @param name + * @return + */ + static ExecutorService createVirtualThreadExecutor(String name) { + try { + final Method virtualThreadBuilderMethod = Arrays.stream(Thread.class.getMethods()) + .filter(m -> m.getName().equals("ofVirtual")) + .findAny().orElseThrow(); + Object virtualThreadBuilder = virtualThreadBuilderMethod.invoke(null); + final Method nameVirtualThreadBuilderMethod = Arrays.stream(virtualThreadBuilderMethod.getReturnType().getMethods()) + .filter(m -> m.getName().equals("name") && m.getParameterCount() == 2) + .findAny().orElseThrow(); + virtualThreadBuilder = nameVirtualThreadBuilderMethod.invoke(virtualThreadBuilder, name, 10000L); + final Method factoryVirtualThreadBuilderMethod = Arrays.stream(Class.forName("java.lang.Thread$Builder").getMethods()) + .filter(m -> m.getName().equals("factory")) + .findAny().orElseThrow(); + final ThreadFactory factory = (ThreadFactory) factoryVirtualThreadBuilderMethod.invoke(virtualThreadBuilder); + final Method newThreadPerTaskExecutorMethod = Arrays.stream(Executors.class.getMethods()) + .filter(m -> m.getName().equals("newThreadPerTaskExecutor") && m.getGenericParameterTypes()[0].equals(ThreadFactory.class)) + .findAny().orElseThrow(); + return (ExecutorService) newThreadPerTaskExecutorMethod.invoke(null, factory); + } catch (ReflectiveOperationException e) { + throw new IllegalStateException("Fail :-)", e); + } + } + + public static void main(String[] args) throws InterruptedException { + final ExecutorService executor = createVirtualThreadExecutor("meh-"); + executor.submit(() -> { + try { + final Method currentThreadMethod = Thread.class.getDeclaredMethod("currentThread"); + final Thread currentThread = (Thread) currentThreadMethod.invoke(null); + final Method isVirtualMethod = Thread.class.getDeclaredMethod("isVirtual"); + final boolean isVirtual = (boolean) isVirtualMethod.invoke(currentThread); + System.out.println("Hello from a " + (isVirtual ? "virtual" : "") + " thread called " + currentThread.getName()); + final Field interruptedField = Thread.class.getDeclaredField("interrupted"); + interruptedField.setAccessible(true); + System.out.println("interrupted: " + interruptedField.getBoolean(currentThread)); + final Method holdsLockMethod = Arrays.stream(Thread.class.getMethods()) + .filter(m -> m.getName().equals("holdsLock") && m.getGenericParameterTypes()[0].equals(Object.class)) + .findAny().orElseThrow(); + System.out.println("holdsLock: " + ((boolean) holdsLockMethod.invoke(null, new Object()))); + final Method threadIdMethod = Thread.class.getDeclaredMethod("threadId"); + System.out.println("getId: " + ((long) threadIdMethod.invoke(currentThread))); + final Method getNextThreadIdOffsetMethod = Thread.class.getDeclaredMethod("getNextThreadIdOffset"); + getNextThreadIdOffsetMethod.setAccessible(true); + System.out.println("getNextThreadIdOffset: " + (long) getNextThreadIdOffsetMethod.invoke(null)); + } catch (Exception e) { + e.printStackTrace(); + } + }); + executor.shutdown(); + executor.awaitTermination(1, java.util.concurrent.TimeUnit.SECONDS); + } +} diff --git a/pom.xml b/pom.xml index 2e0fa88..2fb1045 100755 --- a/pom.xml +++ b/pom.xml @@ -89,6 +89,7 @@ apps/debug-symbols-smoke apps/helidon-quickstart-se apps/imageio + apps/jdkreflections apps/jfr-native-image-performance apps/quarkus-full-microprofile apps/quarkus-json diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java b/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java index 5144995..72b1b6b 100644 --- a/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/AppReproducersTest.java @@ -858,6 +858,63 @@ public void monitorFieldOffsetNOK(TestInfo testInfo, Apps app) throws IOExceptio } } + @Test + @Tag("builder-image") + @IfMandrelVersion(minJDK = "21.0.3", inContainer = true) + public void jdkReflectionsContainerTest(TestInfo testInfo) throws IOException, InterruptedException { + jdkReflections(testInfo, Apps.JDK_REFLECTIONS_BUILDER_IMAGE); + } + + @Test + @IfMandrelVersion(minJDK = "21.0.3") + public void jdkReflectionsTest(TestInfo testInfo) throws IOException, InterruptedException { + jdkReflections(testInfo, Apps.JDK_REFLECTIONS); + } + + public void jdkReflections(TestInfo testInfo, Apps app) throws IOException, InterruptedException { + LOGGER.info("Testing app: " + app); + Process process = null; + File buildLog = null; + File runLog = null; + final StringBuilder report = new StringBuilder(); + final File appDir = Path.of(BASE_DIR, app.dir).toFile(); + final String cn = testInfo.getTestClass().get().getCanonicalName(); + final String mn = testInfo.getTestMethod().get().getName(); + final boolean inContainer = app.runtimeContainer != ContainerNames.NONE; + try { + // Cleanup + cleanTarget(app); + if (inContainer) { + removeContainers(app.runtimeContainer.name); + } + Files.createDirectories(Paths.get(appDir.getAbsolutePath() + File.separator + "logs")); + buildLog = Path.of(appDir.getAbsolutePath(), "logs", "build.log").toFile(); + runLog = Path.of(appDir.getAbsolutePath(), "logs", "run.log").toFile(); + builderRoutine(app, report, cn, mn, appDir, buildLog); + LOGGER.info("Running..."); + final List cmd = getRunCommand(app.buildAndRunCmds.runCommands[0]); + process = runCommand(cmd, appDir, runLog, app); + assertNotNull(process, "The test application failed to run. Check " + getLogsDir(cn, mn) + File.separator + buildLog.getName() + + " and also check https://github.com/graalvm/graalvm-community-jdk21u/issues/28"); + process.waitFor(5, TimeUnit.SECONDS); + Logs.appendln(report, appDir.getAbsolutePath()); + Logs.appendlnSection(report, String.join(" ", cmd)); + Logs.checkLog(cn, mn, app, buildLog); + Logs.checkLog(cn, mn, app, runLog); + processStopper(process, true); + final Pattern p = Pattern.compile(".*Hello from a virtual thread called meh-10000.*"); + assertTrue(searchLogLines(p, runLog, Charset.defaultCharset()), + "Expected pattern " + p + " was not found in the log. Check " + getLogsDir(cn, mn) + File.separator + runLog.getName() + + " and also check https://github.com/graalvm/graalvm-community-jdk21u/issues/28"); + final Pattern p1 = Pattern.compile(".*java.lang.NoSuchMethodException: java.lang.Thread.getNextThreadIdOffset.*"); + assertTrue(searchLogLines(p, runLog, Charset.defaultCharset()), + "Expected pattern " + p1 + " was not found in the log. Check " + getLogsDir(cn, mn) + File.separator + runLog.getName() + + ". The method getNextThreadIdOffset is deleted from native-image intentionally."); + } finally { + cleanup(process, cn, mn, report, app, buildLog, runLog); + } + } + @Test @Tag("builder-image") @IfMandrelVersion(min = "23.1.5", max = "23.1.999", inContainer = true) diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java index 87f40c5..1b3acde 100755 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Apps.java @@ -203,7 +203,17 @@ public enum Apps { URLContent.NONE, WhitelistLogLines.FOR_SERIALIZATION, BuildAndRunCmds.FOR_SERIALIZATION_BUILDER_IMAGE, - ContainerNames.FOR_SERIALIZATION_BUILDER_IMAGE); + ContainerNames.FOR_SERIALIZATION_BUILDER_IMAGE), + JDK_REFLECTIONS("apps" + File.separator + "jdkreflections", + URLContent.NONE, + WhitelistLogLines.JDK_REFLECTIONS, + BuildAndRunCmds.JDK_REFLECTIONS, + ContainerNames.NONE), + JDK_REFLECTIONS_BUILDER_IMAGE("apps" + File.separator + "jdkreflections", + URLContent.NONE, + WhitelistLogLines.JDK_REFLECTIONS, + BuildAndRunCmds.JDK_REFLECTIONS_BUILDER_IMAGE, + ContainerNames.JDK_REFLECTIONS_BUILDER_IMAGE); public final String dir; public final URLContent urlContent; diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java index 2e25a86..5c1e4d9 100755 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/BuildAndRunCmds.java @@ -226,6 +226,32 @@ public enum BuildAndRunCmds { new String[][] { { IS_THIS_WINDOWS ? "target\\timezones.exe" : "./target/timezones" } } ), + JDK_REFLECTIONS( + new String[][] { + { "mvn", "package" }, + { "java", "--add-opens=java.base/java.lang=ALL-UNNAMED", "-agentlib:native-image-agent=config-output-dir=./target/AGENT", + "-cp", "target/jdkreflections.jar", "jdkreflections.Main" }, + { "native-image", "-J--add-opens=java.base/java.lang=ALL-UNNAMED", "-H:ConfigurationFileDirectories=./target/AGENT", + "--link-at-build-time=", "--no-fallback", "-march=native", "-jar", "target/jdkreflections.jar", "target/jdkreflections" } + }, + new String[][] { + { IS_THIS_WINDOWS ? "target\\jdkreflections.exe" : "./target/jdkreflections" } } + ), + JDK_REFLECTIONS_BUILDER_IMAGE( + new String[][] { + { "mvn", "package" }, + { CONTAINER_RUNTIME, "run", IS_THIS_WINDOWS ? "" : "-u", IS_THIS_WINDOWS ? "" : getUnixUIDGID(), + "-t", "--entrypoint", "java", "-v", BASE_DIR + File.separator + "apps" + File.separator + "jdkreflections:/project:z", + BUILDER_IMAGE, "--add-opens=java.base/java.lang=ALL-UNNAMED", + "-agentlib:native-image-agent=config-output-dir=./target/AGENT", "-cp", "target/jdkreflections.jar", "jdkreflections.Main" }, + { CONTAINER_RUNTIME, "run", IS_THIS_WINDOWS ? "" : "-u", IS_THIS_WINDOWS ? "" : getUnixUIDGID(), + "-v", BASE_DIR + File.separator + "apps" + File.separator + "jdkreflections:/project:z", + BUILDER_IMAGE, "-J--add-opens=java.base/java.lang=ALL-UNNAMED", + "-H:ConfigurationFileDirectories=./target/AGENT", "--link-at-build-time=", "--no-fallback", "-march=native", + "-jar", "target/jdkreflections.jar", "target/jdkreflections" } }, + new String[][] { + { IS_THIS_WINDOWS ? "target\\jdkreflections.exe" : "./target/jdkreflections" } } + ), CALENDARS( new String[][] { { "mvn", "package" }, @@ -525,7 +551,7 @@ private static String[] hyperfoil() { } } - final String[][] buildCommands; + public final String[][] buildCommands; public final String[][] runCommands; BuildAndRunCmds(String[][] buildCommands, String[][] runCommands) diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java index 3cfc766..40a0d3e 100644 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/Commands.java @@ -293,7 +293,7 @@ public static Process runCommand(List command, File directory, File logF if (logFile != null) { final String c = "Command: " + String.join(" ", command) + "\n"; LOGGER.infof("Command: %s", command); - Files.write(logFile.toPath(), c.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND); + Files.write(logFile.toPath(), c.getBytes(StandardCharsets.UTF_8), StandardOpenOption.APPEND, StandardOpenOption.CREATE); processBuilder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile)); } if (input != null) { diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java index 27b5bc4..a7a2bb2 100755 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/ContainerNames.java @@ -34,6 +34,7 @@ public enum ContainerNames { HYPERFOIL("hyperfoil-container"), MONITOR_OFFSET_BUILDER_IMAGE("my-monitor-offset-runner"), FOR_SERIALIZATION_BUILDER_IMAGE("my-for-serialization-runner"), + JDK_REFLECTIONS_BUILDER_IMAGE("my-jdkreflections-runner"), NONE("NO_CONTAINER"); public final String name; diff --git a/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java b/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java index c881e0b..a1b9267 100755 --- a/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java +++ b/testsuite/src/it/java/org/graalvm/tests/integration/utils/WhitelistLogLines.java @@ -362,6 +362,13 @@ public Pattern[] get(boolean inContainer) { Pattern.compile(".*sun.reflect.ReflectionFactory is internal proprietary API.*") }; } + }, + JDK_REFLECTIONS { + @Override + public Pattern[] get(boolean inContainer) { + return new Pattern[]{ + }; + } }; public abstract Pattern[] get(boolean inContainer);