From ff4b5cfcfb9bf4924cda09d64c2d39900219c1be Mon Sep 17 00:00:00 2001 From: Tomas Zezula Date: Mon, 24 Nov 2025 10:25:43 +0100 Subject: [PATCH 1/2] [GR-71295] Build/UnitTest Job Failure: Timeout/Thread Block Detected in Truffle Suite. --- .../truffle/api/test/SubprocessTestUtils.java | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java index 1e88dfc3bcbb..c15e6b406772 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java @@ -127,12 +127,19 @@ public final class SubprocessTestUtils { private static final boolean DEBUG_SUBPROCESSES = Boolean.getBoolean("SubprocessTestUtils.javaDebugger"); /** - * Recommended value of the subprocess timeout. After exceeding it, the process is forcibly + * The default subprocess timeout. If the subprocess exceeds this duration, it is forcibly * terminated. * * @see Builder#timeout(Duration) */ - public static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(5); + public static final Duration DEFAULT_TIMEOUT = Duration.ofMinutes(2); + + /** + * Disables the subprocess timeout. When set, the subprocess is allowed to run indefinitely. + * + * @see Builder#timeout(Duration) + */ + public static final Duration NO_TIMEOUT = Duration.ZERO; private static final String CONFIGURED_PROPERTY = SubprocessTestUtils.class.getSimpleName() + ".configured"; @@ -468,7 +475,7 @@ public static final class Builder { private final List prefixVmArgs = new ArrayList<>(); private final List postfixVmArgs = new ArrayList<>(); private boolean failOnNonZeroExit = true; - private Duration timeout; + private Duration timeout = DEFAULT_TIMEOUT; private Consumer onExit; private Consumer onStart; private boolean removeOptimizedRuntimeOptions; @@ -528,9 +535,11 @@ public Builder failOnNonZeroExit(boolean b) { /** * Sets the subprocess timeout. After its expiration, the subprocess is forcibly terminated. - * By default, there is no timeout and the subprocess execution time is not limited. + * By default, the subprocess timeout is {@link #DEFAULT_TIMEOUT}. To set no subprocess + * timeout, use {@link #NO_TIMEOUT}. * * @see SubprocessTestUtils#DEFAULT_TIMEOUT + * @see SubprocessTestUtils#NO_TIMEOUT * */ public Builder timeout(Duration duration) { @@ -776,7 +785,7 @@ private static Subprocess process(List command, Map env, } BufferedReader stdout = new BufferedReader(new InputStreamReader(process.getInputStream())); List output = new ArrayList<>(); - if (timeout == null) { + if (timeout == NO_TIMEOUT) { String line; while ((line = stdout.readLine()) != null) { output.add(line); From b9f9f82b73dfa21b0fee4e5ca79e58ddd4b18ffb Mon Sep 17 00:00:00 2001 From: Tomas Zezula Date: Mon, 24 Nov 2025 15:06:52 +0100 Subject: [PATCH 2/2] GCUtils should be used in a separate VM. --- .../api/debug/test/DebuggerSessionTest.java | 46 +++-- .../truffle/api/dsl/test/WeakCachedTest.java | 174 ++++++++++-------- .../test/GradualInstrumentationTest.java | 106 ++++++----- .../truffle/api/test/SubprocessTestUtils.java | 23 ++- .../api/test/polyglot/ContextAPITest.java | 75 ++++---- .../api/test/polyglot/LoggingTest.java | 121 ++++++------ .../test/polyglot/PolyglotCachingTest.java | 51 ++--- 7 files changed, 348 insertions(+), 248 deletions(-) diff --git a/truffle/src/com.oracle.truffle.api.debug.test/src/com/oracle/truffle/api/debug/test/DebuggerSessionTest.java b/truffle/src/com.oracle.truffle.api.debug.test/src/com/oracle/truffle/api/debug/test/DebuggerSessionTest.java index 1929754324bb..fc49c53e7ec5 100644 --- a/truffle/src/com.oracle.truffle.api.debug.test/src/com/oracle/truffle/api/debug/test/DebuggerSessionTest.java +++ b/truffle/src/com.oracle.truffle.api.debug.test/src/com/oracle/truffle/api/debug/test/DebuggerSessionTest.java @@ -63,7 +63,9 @@ import java.util.zip.ZipEntry; import java.util.zip.ZipOutputStream; +import com.oracle.truffle.api.test.SubprocessTestUtils; import org.graalvm.collections.Pair; +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; import org.graalvm.polyglot.Instrument; @@ -1010,27 +1012,43 @@ private static void checkResolvedSourceSection(SourceSection sourceSection, int } @Test - public void testDebuggedSourcesCanBeReleasedAbsolute() { - testDebuggedSourcesCanBeReleased(() -> { - return Source.newBuilder(InstrumentationTestLanguage.ID, "STATEMENT", "file").cached(false).buildLiteral(); + public void testDebuggedSourcesCanBeReleasedAbsolute() throws IOException, InterruptedException { + runInSubprocess(() -> { + testDebuggedSourcesCanBeReleased(() -> { + return Source.newBuilder(InstrumentationTestLanguage.ID, "STATEMENT", "file").cached(false).buildLiteral(); + }); }); } @Test - public void testDebuggedSourcesCanBeReleasedRelative() throws IOException { - String sourceContent = "\n relative source\nVarA"; - String relativePath = "relative/test.file"; - Path testSourcePath = Files.createTempDirectory("testPath").toRealPath(); - Files.createDirectory(testSourcePath.resolve("relative")); - Path filePath = testSourcePath.resolve(relativePath); - Files.write(filePath, sourceContent.getBytes()); - testDebuggedSourcesCanBeReleased(() -> { - TestDebugNoContentLanguage language = new TestDebugNoContentLanguage(relativePath, true, true); - ProxyLanguage.setDelegate(language); - return Source.newBuilder(ProxyLanguage.ID, sourceContent, "file").cached(false).buildLiteral(); + public void testDebuggedSourcesCanBeReleasedRelative() throws IOException, InterruptedException { + runInSubprocess(() -> { + String sourceContent = "\n relative source\nVarA"; + String relativePath = "relative/test.file"; + try { + Path testSourcePath = Files.createTempDirectory("testPath").toRealPath(); + Files.createDirectory(testSourcePath.resolve("relative")); + Path filePath = testSourcePath.resolve(relativePath); + Files.write(filePath, sourceContent.getBytes()); + } catch (IOException ioe) { + throw new AssertionError(ioe); + } + testDebuggedSourcesCanBeReleased(() -> { + TestDebugNoContentLanguage language = new TestDebugNoContentLanguage(relativePath, true, true); + ProxyLanguage.setDelegate(language); + return Source.newBuilder(ProxyLanguage.ID, sourceContent, "file").cached(false).buildLiteral(); + }); }); } + private static void runInSubprocess(Runnable runnable) throws IOException, InterruptedException { + if (ImageInfo.inImageCode()) { + runnable.run(); + } else { + SubprocessTestUtils.newBuilder(DebuggerSessionTest.class, runnable).run(); + } + } + private void testDebuggedSourcesCanBeReleased(Supplier sourceFactory) { try (DebuggerSession session = tester.startSession()) { GCUtils.assertObjectsCollectible(iteration -> { diff --git a/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/WeakCachedTest.java b/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/WeakCachedTest.java index b5ef0de9b3f8..8a9558cf8bc1 100644 --- a/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/WeakCachedTest.java +++ b/truffle/src/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/WeakCachedTest.java @@ -42,10 +42,13 @@ import static org.junit.Assert.assertNotNull; +import java.io.IOException; import java.lang.ref.WeakReference; import java.util.List; import java.util.concurrent.Semaphore; +import com.oracle.truffle.api.test.SubprocessTestUtils; +import org.graalvm.nativeimage.ImageInfo; import org.junit.Test; import com.oracle.truffle.api.CompilerDirectives; @@ -73,13 +76,23 @@ public class WeakCachedTest extends AbstractPolyglotTest { @Test - public void testWeakSimpleNode() { - WeakSimpleNode node = WeakSimpleNodeGen.create(); - Object o = new String(""); - WeakReference ref = new WeakReference<>(o); - node.execute(o); - o = null; - GCUtils.assertGc("Reference is not collected", ref); + public void testWeakSimpleNode() throws IOException, InterruptedException { + runInSubprocess(() -> { + WeakSimpleNode node = WeakSimpleNodeGen.create(); + Object o = new String(""); + WeakReference ref = new WeakReference<>(o); + node.execute(o); + o = null; + GCUtils.assertGc("Reference is not collected", ref); + }); + } + + private static void runInSubprocess(Runnable runnable) throws IOException, InterruptedException { + if (ImageInfo.inImageCode()) { + runnable.run(); + } else { + SubprocessTestUtils.newBuilder(WeakCachedTest.class, runnable).run(); + } } @GenerateUncached @@ -96,26 +109,28 @@ Object s0(String arg, } @Test - public void testWeakInlineCache() { - WeakInlineCacheNode node = WeakInlineCacheNodeGen.create(); - Object o0 = new String(""); - Object o1 = new String(""); - Object o2 = new String(""); - WeakReference ref0 = new WeakReference<>(o0); - WeakReference ref1 = new WeakReference<>(o1); - WeakReference ref2 = new WeakReference<>(o2); - node.execute(o0); - node.execute(o1); - o0 = null; - GCUtils.assertGc("Reference is not collected", ref0); - - node.execute(o1); - node.execute(o2); - o1 = null; - o2 = null; - GCUtils.assertGc("Reference is not collected", List.of(ref1, ref2)); - - assertFails(() -> node.execute(new String("")), UnsupportedSpecializationException.class); + public void testWeakInlineCache() throws IOException, InterruptedException { + runInSubprocess(() -> { + WeakInlineCacheNode node = WeakInlineCacheNodeGen.create(); + Object o0 = new String(""); + Object o1 = new String(""); + Object o2 = new String(""); + WeakReference ref0 = new WeakReference<>(o0); + WeakReference ref1 = new WeakReference<>(o1); + WeakReference ref2 = new WeakReference<>(o2); + node.execute(o0); + node.execute(o1); + o0 = null; + GCUtils.assertGc("Reference is not collected", ref0); + + node.execute(o1); + node.execute(o2); + o1 = null; + o2 = null; + GCUtils.assertGc("Reference is not collected", List.of(ref1, ref2)); + + assertFails(() -> node.execute(new String("")), UnsupportedSpecializationException.class); + }); } @GenerateUncached @@ -133,26 +148,28 @@ Object s0(String arg, } @Test - public void testWeakCachedLibrary() { - WeakCachedLibraryNode node = adoptNode(WeakCachedLibraryNodeGen.create()).get(); - Object o0 = new String(""); - Object o1 = new String(""); - Object o2 = new String(""); - WeakReference ref0 = new WeakReference<>(o0); - WeakReference ref1 = new WeakReference<>(o1); - WeakReference ref2 = new WeakReference<>(o2); - node.execute(o0); - node.execute(o1); - o0 = null; - GCUtils.assertGc("Reference is not collected", ref0); - - node.execute(o1); - node.execute(o2); - o1 = null; - o2 = null; - GCUtils.assertGc("Reference is not collected", List.of(ref1, ref2)); - - assertFails(() -> node.execute(new String("")), UnsupportedSpecializationException.class); + public void testWeakCachedLibrary() throws InterruptedException, IOException { + runInSubprocess(() -> { + WeakCachedLibraryNode node = adoptNode(WeakCachedLibraryNodeGen.create()).get(); + Object o0 = new String(""); + Object o1 = new String(""); + Object o2 = new String(""); + WeakReference ref0 = new WeakReference<>(o0); + WeakReference ref1 = new WeakReference<>(o1); + WeakReference ref2 = new WeakReference<>(o2); + node.execute(o0); + node.execute(o1); + o0 = null; + GCUtils.assertGc("Reference is not collected", ref0); + + node.execute(o1); + node.execute(o2); + o1 = null; + o2 = null; + GCUtils.assertGc("Reference is not collected", List.of(ref1, ref2)); + + assertFails(() -> node.execute(new String("")), UnsupportedSpecializationException.class); + }); } @GenerateUncached @@ -181,14 +198,16 @@ Object s0replace(String arg) { } @Test - public void testWeakSharedNode() { - WeakSharedCacheNode node = WeakSharedCacheNodeGen.create(); - Object o0 = new String(""); - WeakReference ref1 = new WeakReference<>(o0); - node.execute(o0, false); - o0 = null; - GCUtils.assertGc("Reference is not collected", ref1); - node.execute("", false); + public void testWeakSharedNode() throws IOException, InterruptedException { + runInSubprocess(() -> { + WeakSharedCacheNode node = WeakSharedCacheNodeGen.create(); + Object o0 = new String(""); + WeakReference ref1 = new WeakReference<>(o0); + node.execute(o0, false); + o0 = null; + GCUtils.assertGc("Reference is not collected", ref1); + node.execute("", false); + }); } @GenerateUncached @@ -217,28 +236,33 @@ Object s1(String arg, boolean expectNull, * reference cannot get collected. */ @Test - public void testConsistentGuardAndSpecialization() throws InterruptedException { - ConsistentGuardAndSpecializationNode node = ConsistentGuardAndSpecializationNodeGen.create(); - Object o0 = new String(""); - WeakReference ref1 = new WeakReference<>(o0); - node.execute(o0); - Thread t = new Thread(new Runnable() { - public void run() { - node.locksEnabled = true; - node.execute(new String("")); + public void testConsistentGuardAndSpecialization() throws IOException, InterruptedException { + runInSubprocess(() -> { + ConsistentGuardAndSpecializationNode node = ConsistentGuardAndSpecializationNodeGen.create(); + Object o0 = new String(""); + WeakReference ref1 = new WeakReference<>(o0); + node.execute(o0); + Thread t = new Thread(new Runnable() { + public void run() { + node.locksEnabled = true; + node.execute(new String("")); + } + }); + t.start(); + try { + node.waitForGuard.acquire(); + o0 = null; + try { + GCUtils.assertNotGc("Reference is not collected", ref1); + } finally { + node.waitForSpecialization.release(); + t.join(); + } + } catch (InterruptedException ie) { + throw new AssertionError(ie); } + GCUtils.assertGc("Reference is not collected", ref1); }); - t.start(); - node.waitForGuard.acquire(); - o0 = null; - try { - GCUtils.assertNotGc("Reference is not collected", ref1); - } finally { - node.waitForSpecialization.release(); - t.join(); - } - GCUtils.assertGc("Reference is not collected", ref1); - } @Test diff --git a/truffle/src/com.oracle.truffle.api.instrumentation.test/src/com/oracle/truffle/api/instrumentation/test/GradualInstrumentationTest.java b/truffle/src/com.oracle.truffle.api.instrumentation.test/src/com/oracle/truffle/api/instrumentation/test/GradualInstrumentationTest.java index 8e395c6b8184..134a5ebb4233 100644 --- a/truffle/src/com.oracle.truffle.api.instrumentation.test/src/com/oracle/truffle/api/instrumentation/test/GradualInstrumentationTest.java +++ b/truffle/src/com.oracle.truffle.api.instrumentation.test/src/com/oracle/truffle/api/instrumentation/test/GradualInstrumentationTest.java @@ -46,6 +46,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import java.io.IOException; import java.lang.ref.WeakReference; import java.lang.reflect.Field; import java.util.ArrayList; @@ -53,6 +54,8 @@ import java.util.List; import java.util.stream.Collectors; +import com.oracle.truffle.api.test.SubprocessTestUtils; +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Source; import org.junit.After; @@ -322,51 +325,66 @@ public void testRetiredTreeOfRetiredTreeStillReachable() throws InterruptedExcep */ @SuppressWarnings("unchecked") @Test - public void testWrappersAreRemoved() throws InterruptedException, NoSuchFieldException, IllegalAccessException { - Source source = Source.create(ID, "ROOT(MATERIALIZE_CHILD_STATEMENT(LOOP(0, STATEMENT), LOOP(3, STATEMENT(CONSTANT(42)))))"); - RecordingExecutionEventListener listener1 = attachRecordingListener(true, InstrumentationTestLanguage.LoopTag.class); - Thread t1 = evalInNewThread(source, listener1); - listener1.go("$START", "+L"); - listener1.disableSteppingWhileWaiting(); - RecordingExecutionEventListener listener2 = new RecordingExecutionEventListener(false, true); - EventBinding binding = attachRecordingListener(listener2, StandardTags.StatementTag.class); - context.eval(source); - listener1.go("-L"); - t1.join(); - assertEquals("+S+S-S+S-S+S-S+S-S-S+S-S+S-S+S-S", listener2.getRecording()); - List enteredMaterializedStatementNodes = listener2.getEnteredNodes().stream().filter(node -> node instanceof InstrumentationTestLanguage.MaterializedChildStatementNode).collect( - Collectors.toList()); - // only the materialized node with reference to the retired tree - assertEquals(1, enteredMaterializedStatementNodes.size()); - List> retiredStatementNodes = new ArrayList<>(); - for (Node enteredNode : enteredMaterializedStatementNodes) { - assertTrue(enteredNode.getParent() instanceof InstrumentableNode.WrapperNode); - InstrumentableNode.WrapperNode wrapperNode = (InstrumentableNode.WrapperNode) enteredNode.getParent(); - Class probeNodeClass = wrapperNode.getProbeNode().getClass(); - Field retiredNodeReferenceField = probeNodeClass.getDeclaredField("retiredNodeReference"); - retiredNodeReferenceField.setAccessible(true); - Object retiredNodeReference = retiredNodeReferenceField.get(wrapperNode.getProbeNode()); - assertNotNull(retiredNodeReference); - Class retiredNodeReferenceClass = retiredNodeReference.getClass(); - Field retiredNodeWeakReferenceField = retiredNodeReferenceClass.getDeclaredField("node"); - retiredNodeWeakReferenceField.setAccessible(true); - Object retiredNodeWeakReference = retiredNodeWeakReferenceField.get(retiredNodeReference); - assertNotNull(retiredNodeWeakReference); - assertTrue(retiredNodeWeakReference instanceof WeakReference); - retiredStatementNodes.add((WeakReference) retiredNodeWeakReference); - } - /* - * Clear entered nodes before calling gc so that the old subtree root is no longer reachable - * via parents. - */ - listener2.clearEnteredNodes(); - GCUtils.assertGc("Retired node is was not collected!", retiredStatementNodes); - binding.dispose(); - context.eval(source); - for (Node enteredNode : enteredMaterializedStatementNodes) { - assertFalse(enteredNode.getParent() instanceof InstrumentableNode.WrapperNode); + public void testWrappersAreRemoved() throws IOException, InterruptedException { + Runnable runnable = () -> { + Source source = Source.create(ID, "ROOT(MATERIALIZE_CHILD_STATEMENT(LOOP(0, STATEMENT), LOOP(3, STATEMENT(CONSTANT(42)))))"); + RecordingExecutionEventListener listener1 = attachRecordingListener(true, InstrumentationTestLanguage.LoopTag.class); + Thread t1 = evalInNewThread(source, listener1); + listener1.go("$START", "+L"); + listener1.disableSteppingWhileWaiting(); + RecordingExecutionEventListener listener2 = new RecordingExecutionEventListener(false, true); + EventBinding binding = attachRecordingListener(listener2, StandardTags.StatementTag.class); + context.eval(source); + listener1.go("-L"); + try { + t1.join(); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + assertEquals("+S+S-S+S-S+S-S+S-S-S+S-S+S-S+S-S", listener2.getRecording()); + List enteredMaterializedStatementNodes = listener2.getEnteredNodes().stream().filter(node -> node instanceof InstrumentationTestLanguage.MaterializedChildStatementNode).collect( + Collectors.toList()); + // only the materialized node with reference to the retired tree + assertEquals(1, enteredMaterializedStatementNodes.size()); + List> retiredStatementNodes = new ArrayList<>(); + for (Node enteredNode : enteredMaterializedStatementNodes) { + try { + assertTrue(enteredNode.getParent() instanceof InstrumentableNode.WrapperNode); + InstrumentableNode.WrapperNode wrapperNode = (InstrumentableNode.WrapperNode) enteredNode.getParent(); + Class probeNodeClass = wrapperNode.getProbeNode().getClass(); + Field retiredNodeReferenceField = probeNodeClass.getDeclaredField("retiredNodeReference"); + retiredNodeReferenceField.setAccessible(true); + Object retiredNodeReference = retiredNodeReferenceField.get(wrapperNode.getProbeNode()); + assertNotNull(retiredNodeReference); + Class retiredNodeReferenceClass = retiredNodeReference.getClass(); + Field retiredNodeWeakReferenceField = retiredNodeReferenceClass.getDeclaredField("node"); + retiredNodeWeakReferenceField.setAccessible(true); + Object retiredNodeWeakReference = retiredNodeWeakReferenceField.get(retiredNodeReference); + assertNotNull(retiredNodeWeakReference); + assertTrue(retiredNodeWeakReference instanceof WeakReference); + retiredStatementNodes.add((WeakReference) retiredNodeWeakReference); + } catch (ReflectiveOperationException re) { + throw new AssertionError(re); + } + } + /* + * Clear entered nodes before calling gc so that the old subtree root is no longer + * reachable via parents. + */ + listener2.clearEnteredNodes(); + GCUtils.assertGc("Retired node is was not collected!", retiredStatementNodes); + binding.dispose(); + context.eval(source); + for (Node enteredNode : enteredMaterializedStatementNodes) { + assertFalse(enteredNode.getParent() instanceof InstrumentableNode.WrapperNode); + } + assertEquals("+L+L-L+L-L-L+L-L+L-L+L-L", listener1.getRecording()); + }; + if (ImageInfo.inImageCode()) { + runnable.run(); + } else { + SubprocessTestUtils.newBuilder(GradualInstrumentationTest.class, runnable).run(); } - assertEquals("+L+L-L+L-L-L+L-L+L-L+L-L", listener1.getRecording()); } public static class RecordingExecutionEventListener implements ExecutionEventListener { diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java index c15e6b406772..1c327c4ac50b 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/SubprocessTestUtils.java @@ -66,6 +66,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.Formatter; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -97,6 +98,7 @@ import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; +import org.junit.experimental.theories.Theory; import org.junit.rules.TestRule; import org.junit.rules.Timeout; import org.junit.runner.Description; @@ -181,16 +183,23 @@ public static boolean isSubprocess() { private static Method findTestMethod(Class testClass) { StackTraceElement[] stack = Thread.currentThread().getStackTrace(); if (stack != null) { + Map testMethodByName = new HashMap<>(); + for (Method method : testClass.getDeclaredMethods()) { + if (method.getAnnotation(Test.class) != null || method.getAnnotation(Theory.class) != null) { + Method prev = testMethodByName.put(method.getName(), method); + if (prev != null) { + throw new IllegalStateException(String.format("Multiple test methods with the same name '%s' found in class %s. Conflicting methods: %s and %s.", + method.getName(), testClass.getName(), prev, method)); + } + } + } + for (int i = stack.length - 1; i >= 0; i--) { StackTraceElement element = stack[i]; if (testClass.getName().equals(element.getClassName())) { - try { - Method method = testClass.getDeclaredMethod(element.getMethodName()); - if (method.getAnnotation(Test.class) != null) { - return method; - } - } catch (NoSuchMethodException noSuchMethodException) { - // skip methods with arguments. + Method method = testMethodByName.get(element.getMethodName()); + if (method != null) { + return method; } } } diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/ContextAPITest.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/ContextAPITest.java index 56b903ec733c..122cbbdf7dee 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/ContextAPITest.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/ContextAPITest.java @@ -75,7 +75,9 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import com.oracle.truffle.api.test.SubprocessTestUtils; import com.oracle.truffle.api.test.common.TestUtils; +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.options.OptionCategory; import org.graalvm.options.OptionDescriptors; import org.graalvm.options.OptionKey; @@ -594,41 +596,52 @@ private static void testEnterLeave(Context context, int depth) { } @Theory - public void testMultithreadedEnterLeave(boolean vthreads) throws InterruptedException, ExecutionException { - Assume.assumeFalse(vthreads && !canCreateVirtualThreads()); - Context c = Context.create(); - Set> threads = new HashSet<>(); - ExecutorService service = Executors.newFixedThreadPool(20, (run) -> { - Thread t = vthreads ? Thread.ofVirtual().unstarted(run) : new Thread(run); - threads.add(new WeakReference<>(t)); - return t; - }); - - List> futures = new ArrayList<>(); - - for (int i = 0; i < 200; i++) { - futures.add(service.submit(() -> testEnterLeave(c, 0))); - } + public void testMultithreadedEnterLeave(boolean vthreads) throws Exception { + Runnable runnable = () -> { + try { + Assume.assumeFalse(vthreads && !canCreateVirtualThreads()); + Context c = Context.create(); + Set> threads = new HashSet<>(); + ExecutorService service = Executors.newFixedThreadPool(20, (run) -> { + Thread t = vthreads ? Thread.ofVirtual().unstarted(run) : new Thread(run); + threads.add(new WeakReference<>(t)); + return t; + }); + + List> futures = new ArrayList<>(); + + for (int i = 0; i < 200; i++) { + futures.add(service.submit(() -> testEnterLeave(c, 0))); + } - for (Future future : futures) { - future.get(); - } + for (Future future : futures) { + future.get(); + } - service.shutdown(); - assertTrue("Executor threads did not finish in time.", service.awaitTermination(10, TimeUnit.SECONDS)); - for (Reference threadRef : threads) { - Thread t = threadRef.get(); - if (t != null) { - t.join(10000); + service.shutdown(); + assertTrue("Executor threads did not finish in time.", service.awaitTermination(10, TimeUnit.SECONDS)); + for (Reference threadRef : threads) { + Thread t = threadRef.get(); + if (t != null) { + t.join(10000); + } + } + Reference ref = new WeakReference<>(service); + service = null; + if (!vthreads) { // GR-54640 This fails transiently with virtual threads + GCUtils.assertGc("Nobody holds on the executor anymore", ref); + GCUtils.assertGc("Nobody holds on the thread anymore", threads); + } + c.close(); + } catch (InterruptedException | ExecutionException e) { + throw new AssertionError(e); } + }; + if (ImageInfo.inImageCode()) { + runnable.run(); + } else { + SubprocessTestUtils.newBuilder(ContextAPITest.class, runnable).run(); } - Reference ref = new WeakReference<>(service); - service = null; - if (!vthreads) { // GR-54640 This fails transiently with virtual threads - GCUtils.assertGc("Nobody holds on the executor anymore", ref); - GCUtils.assertGc("Nobody holds on the thread anymore", threads); - } - c.close(); } @Test diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/LoggingTest.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/LoggingTest.java index 24ba3ed1f6de..d3953e300418 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/LoggingTest.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/LoggingTest.java @@ -66,6 +66,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import com.oracle.truffle.api.test.SubprocessTestUtils; +import org.graalvm.nativeimage.ImageInfo; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Engine; import org.junit.After; @@ -529,45 +531,57 @@ public void testPolyglotLogHandler() { } @Test - public void testGarbageCollectedContext() { - TestHandler handler = new TestHandler(); - Context collectedContext = newContextBuilder().options(createLoggingOptions(LoggingLanguageFirst.ID, null, Level.FINEST.toString())).logHandler(handler).build(); - collectedContext.eval(LoggingLanguageFirst.ID, ""); - List> expected = createExpectedLog(LoggingLanguageFirst.ID, Level.FINEST, Collections.emptyMap()); - Assert.assertEquals(expected, handler.getLog()); - Reference collectedContextRef = new WeakReference<>(collectedContext); - collectedContext = null; - GCUtils.assertGc("Cannot free context.", collectedContextRef); - handler = new TestHandler(); - try (Context newContext = newContextBuilder().logHandler(handler).build()) { - newContext.eval(LoggingLanguageFirst.ID, ""); - expected = createExpectedLog(LoggingLanguageFirst.ID, Level.INFO, Collections.emptyMap()); - Assert.assertEquals(expected, handler.getLog()); - } - } - - @Test - public void testGarbageCollectedContext2() { - TestHandler collectedContextHandler = new TestHandler(); - Context collectedContext = newContextBuilder().options(createLoggingOptions(LoggingLanguageFirst.ID, null, Level.FINEST.toString())).logHandler(collectedContextHandler).build(); - TestHandler contextHandler = new TestHandler(); - try (Context context = newContextBuilder().options(createLoggingOptions(LoggingLanguageFirst.ID, null, Level.FINE.toString())).logHandler(contextHandler).build()) { + public void testGarbageCollectedContext() throws IOException, InterruptedException { + runInSubprocess(() -> { + TestHandler handler = new TestHandler(); + Context collectedContext = newContextBuilder().options(createLoggingOptions(LoggingLanguageFirst.ID, null, Level.FINEST.toString())).logHandler(handler).build(); collectedContext.eval(LoggingLanguageFirst.ID, ""); List> expected = createExpectedLog(LoggingLanguageFirst.ID, Level.FINEST, Collections.emptyMap()); - Assert.assertEquals(expected, collectedContextHandler.getLog()); - context.eval(LoggingLanguageFirst.ID, ""); - expected = createExpectedLog(LoggingLanguageFirst.ID, Level.FINE, Collections.emptyMap()); - Assert.assertEquals(expected, contextHandler.getLog()); + Assert.assertEquals(expected, handler.getLog()); Reference collectedContextRef = new WeakReference<>(collectedContext); collectedContext = null; GCUtils.assertGc("Cannot free context.", collectedContextRef); - contextHandler.clear(); - context.eval(LoggingLanguageFirst.ID, ""); - expected = createExpectedLog(LoggingLanguageFirst.ID, Level.FINE, Collections.emptyMap()); - Assert.assertEquals(expected, contextHandler.getLog()); + handler = new TestHandler(); + try (Context newContext = newContextBuilder().logHandler(handler).build()) { + newContext.eval(LoggingLanguageFirst.ID, ""); + expected = createExpectedLog(LoggingLanguageFirst.ID, Level.INFO, Collections.emptyMap()); + Assert.assertEquals(expected, handler.getLog()); + } + }); + } + + private static void runInSubprocess(Runnable runnable) throws IOException, InterruptedException { + if (ImageInfo.inImageCode()) { + runnable.run(); + } else { + SubprocessTestUtils.newBuilder(LoggingTest.class, runnable).run(); } } + @Test + public void testGarbageCollectedContext2() throws IOException, InterruptedException { + runInSubprocess(() -> { + TestHandler collectedContextHandler = new TestHandler(); + Context collectedContext = newContextBuilder().options(createLoggingOptions(LoggingLanguageFirst.ID, null, Level.FINEST.toString())).logHandler(collectedContextHandler).build(); + TestHandler contextHandler = new TestHandler(); + try (Context context = newContextBuilder().options(createLoggingOptions(LoggingLanguageFirst.ID, null, Level.FINE.toString())).logHandler(contextHandler).build()) { + collectedContext.eval(LoggingLanguageFirst.ID, ""); + List> expected = createExpectedLog(LoggingLanguageFirst.ID, Level.FINEST, Collections.emptyMap()); + Assert.assertEquals(expected, collectedContextHandler.getLog()); + context.eval(LoggingLanguageFirst.ID, ""); + expected = createExpectedLog(LoggingLanguageFirst.ID, Level.FINE, Collections.emptyMap()); + Assert.assertEquals(expected, contextHandler.getLog()); + Reference collectedContextRef = new WeakReference<>(collectedContext); + collectedContext = null; + GCUtils.assertGc("Cannot free context.", collectedContextRef); + contextHandler.clear(); + context.eval(LoggingLanguageFirst.ID, ""); + expected = createExpectedLog(LoggingLanguageFirst.ID, Level.FINE, Collections.emptyMap()); + Assert.assertEquals(expected, contextHandler.getLog()); + } + }); + } + @Test public void testLogToStream() { CloseableByteArrayOutputStream stream = new CloseableByteArrayOutputStream(); @@ -1388,27 +1402,30 @@ public void testInterpreterOnlyWarning() { } @Test - public void testGR49739() { - Assume.assumeTrue(GCUtils.isSupported()); - AtomicReference loggerHolder = new AtomicReference<>(); - AbstractLoggingLanguage.action = (ctx, defaultLoggers) -> { - loggerHolder.set(ctx.env.getLogger("after.close")); - return false; - }; - Context.Builder contextBuilder = newContextBuilder(); - contextBuilder.option("log." + LoggingLanguageFirst.ID + ".level", "CONFIG"); - Context ctx = contextBuilder.build(); - Reference ctxRef = new WeakReference<>(ctx); - try { - ctx.eval(LoggingLanguageFirst.ID, ""); - } finally { - ctx.close(); - ctx = null; - } - GCUtils.assertGc("Context should be collected.", ctxRef); - TruffleLogger closedLogger = loggerHolder.getAndSet(null); - Assert.assertNotNull(closedLogger); - AbstractPolyglotTest.assertFails(() -> closedLogger.config("Should fail"), AssertionError.class, (e) -> Assert.assertTrue(e.getMessage().contains("Invalid sharing of bound TruffleLogger"))); + public void testGR49739() throws IOException, InterruptedException { + runInSubprocess(() -> { + Assume.assumeTrue(GCUtils.isSupported()); + AtomicReference loggerHolder = new AtomicReference<>(); + AbstractLoggingLanguage.action = (ctx, defaultLoggers) -> { + loggerHolder.set(ctx.env.getLogger("after.close")); + return false; + }; + Context.Builder contextBuilder = newContextBuilder(); + contextBuilder.option("log." + LoggingLanguageFirst.ID + ".level", "CONFIG"); + Context ctx = contextBuilder.build(); + Reference ctxRef = new WeakReference<>(ctx); + try { + ctx.eval(LoggingLanguageFirst.ID, ""); + } finally { + ctx.close(); + ctx = null; + } + GCUtils.assertGc("Context should be collected.", ctxRef); + TruffleLogger closedLogger = loggerHolder.getAndSet(null); + Assert.assertNotNull(closedLogger); + AbstractPolyglotTest.assertFails(() -> closedLogger.config("Should fail"), AssertionError.class, + (e) -> Assert.assertTrue(e.getMessage().contains("Invalid sharing of bound TruffleLogger"))); + }); } @Test diff --git a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/PolyglotCachingTest.java b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/PolyglotCachingTest.java index 3facddd4e137..a114b9a51afb 100644 --- a/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/PolyglotCachingTest.java +++ b/truffle/src/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/polyglot/PolyglotCachingTest.java @@ -272,20 +272,21 @@ protected Object execute(RootNode node, Env env, Object[] contextArguments, Obje * Test that CallTargets stay cached as long as their source instance is alive. */ @Test - public void testParsedASTIsNotCollectedIfSourceIsAlive() { + public void testParsedASTIsNotCollectedIfSourceIsAlive() throws IOException, InterruptedException { Assume.assumeFalse("This test is too slow in fastdebug.", System.getProperty("java.vm.version").contains("fastdebug")); + runInSubprocess(() -> { + try (Context context = Context.create()) { + // needs to stay alive + Source source = Source.create(CallTargetStoringTestLanguage.ID, "0"); - try (Context context = Context.create()) { - // needs to stay alive - Source source = Source.create(CallTargetStoringTestLanguage.ID, "0"); - - context.eval(source); - evalTestLanguage(context, CallTargetsFreedAssertTestLanguage.class, "", false); - for (int i = 0; i < GCUtils.GC_TEST_ITERATIONS; i++) { - assertEquals("foobar", context.eval(source).asString()); + context.eval(source); + evalTestLanguage(context, CallTargetsFreedAssertTestLanguage.class, "", false); + for (int i = 0; i < GCUtils.GC_TEST_ITERATIONS; i++) { + assertEquals("foobar", context.eval(source).asString()); + } + evalTestLanguage(context, CallTargetsFreedAssertTestLanguage.class, "", false); } - evalTestLanguage(context, CallTargetsFreedAssertTestLanguage.class, "", false); - } + }); } /* @@ -410,24 +411,24 @@ public void testSourceFreeContextStrong() throws Exception { * the cached CallTargets. */ @Test - public void testSourceStrongContextFree() { + public void testSourceStrongContextFree() throws IOException, InterruptedException { TruffleTestAssumptions.assumeWeakEncapsulation(); Assume.assumeFalse("This test is too slow in fastdebug.", System.getProperty("java.vm.version").contains("fastdebug")); + runInSubprocess(() -> { + List survivingSources = new ArrayList<>(); - List survivingSources = new ArrayList<>(); - - for (int i = 0; i < GCUtils.GC_TEST_ITERATIONS; i++) { - try (Context context = Context.newBuilder().build()) { - Source source = Source.create(CallTargetStoringCopySourceTestLanguage.ID, String.valueOf(i)); - assertEquals("foobar", context.eval(source).asString()); - assertEquals("foobar", context.eval(source).asString()); - survivingSources.add(source); + for (int i = 0; i < GCUtils.GC_TEST_ITERATIONS; i++) { + try (Context context = Context.newBuilder().build()) { + Source source = Source.create(CallTargetStoringCopySourceTestLanguage.ID, String.valueOf(i)); + assertEquals("foobar", context.eval(source).asString()); + assertEquals("foobar", context.eval(source).asString()); + survivingSources.add(source); + } } - } - - try (Context context = Context.create()) { - evalTestLanguage(context, CallTargetsFreedAssertTestLanguage.class, "", true); - } + try (Context context = Context.create()) { + evalTestLanguage(context, CallTargetsFreedAssertTestLanguage.class, "", true); + } + }); } /**