diff --git a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonPinnedThreadValidationJunitExtension.java b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonPinnedThreadValidationJunitExtension.java index 4033fef751d..0a0b42593df 100644 --- a/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonPinnedThreadValidationJunitExtension.java +++ b/microprofile/testing/junit5/src/main/java/io/helidon/microprofile/testing/junit5/HelidonPinnedThreadValidationJunitExtension.java @@ -16,50 +16,50 @@ package io.helidon.microprofile.testing.junit5; -import java.util.ArrayList; -import java.util.List; - import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedFrame; import jdk.jfr.consumer.RecordingStream; import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.ExtensionContext; -import static org.junit.jupiter.api.Assertions.fail; - /** * JUnit5 extension to support pinned threads validation. */ class HelidonPinnedThreadValidationJunitExtension implements BeforeAllCallback, AfterAllCallback { - private List jfrVTPinned; private RecordingStream recordingStream; private boolean pinnedThreadValidation; + private PinningException pinningException; @Override public void beforeAll(ExtensionContext context) throws Exception { Class testClass = context.getRequiredTestClass(); pinnedThreadValidation = testClass.getAnnotation(PinnedThreadValidation.class) != null; if (pinnedThreadValidation) { - jfrVTPinned = new ArrayList<>(); recordingStream = new RecordingStream(); recordingStream.enable("jdk.VirtualThreadPinned").withStackTrace(); - recordingStream.onEvent("jdk.VirtualThreadPinned", event -> { - jfrVTPinned.add(new EventWrapper(event)); - }); + recordingStream.onEvent("jdk.VirtualThreadPinned", this::record); recordingStream.startAsync(); } } + void record(RecordedEvent event) { + PinningException e = new PinningException(event); + if (pinningException == null) { + pinningException = e; + } else { + pinningException.addSuppressed(e); + } + } + @Override public void afterAll(ExtensionContext context) { if (pinnedThreadValidation) { try { // Flush ending events recordingStream.stop(); - if (!jfrVTPinned.isEmpty()) { - fail("Some pinned virtual threads were detected:\n" + jfrVTPinned); + if (pinningException != null) { + throw pinningException; } } finally { recordingStream.close(); @@ -67,28 +67,26 @@ public void afterAll(ExtensionContext context) { } } - private static class EventWrapper { - + private static class PinningException extends AssertionError { private final RecordedEvent recordedEvent; - private EventWrapper(RecordedEvent recordedEvent) { + PinningException(RecordedEvent recordedEvent) { this.recordedEvent = recordedEvent; + if (recordedEvent.getStackTrace() != null) { + StackTraceElement[] stackTraceElements = recordedEvent.getStackTrace().getFrames().stream() + .map(f -> new StackTraceElement(f.getMethod().getType().getName(), + f.getMethod().getName(), + f.getMethod().getType().getName() + ".java", + f.getLineNumber())) + .toArray(StackTraceElement[]::new); + super.setStackTrace(stackTraceElements); + } } @Override - public String toString() { - StringBuilder builder = new StringBuilder(recordedEvent.toString()); - if (recordedEvent.getStackTrace() != null) { - builder.append("full-stackTrace = ["); - List frames = recordedEvent.getStackTrace().getFrames(); - for (RecordedFrame frame : frames) { - builder.append("\n\t").append(frame.getMethod().getType().getName()) - .append("#").append(frame.getMethod().getName()) - .append("(").append(frame.getLineNumber()).append(")"); - } - builder.append("\n]"); - } - return builder.toString(); + public String getMessage() { + return "Pinned virtual threads were detected:\n" + + recordedEvent.toString(); } } } diff --git a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java index 47ef512a1bb..8466028baf8 100644 --- a/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java +++ b/microprofile/testing/testng/src/main/java/io/helidon/microprofile/testing/testng/HelidonTestNgListener.java @@ -60,7 +60,6 @@ import jakarta.inject.Singleton; import jakarta.ws.rs.client.ClientBuilder; import jdk.jfr.consumer.RecordedEvent; -import jdk.jfr.consumer.RecordedFrame; import jdk.jfr.consumer.RecordingStream; import org.eclipse.microprofile.config.Config; import org.eclipse.microprofile.config.spi.ConfigBuilder; @@ -73,8 +72,6 @@ import org.testng.ITestResult; import org.testng.annotations.Test; -import static org.testng.Assert.fail; - /** * TestNG extension to support Helidon CDI container in tests. */ @@ -93,7 +90,6 @@ public class HelidonTestNgListener implements IClassListener, ITestListener { private List classLevelExtensions = new ArrayList<>(); private List classLevelBeans = new ArrayList<>(); private ConfigMeta classLevelConfigMeta = new ConfigMeta(); - private List jfrVTPinned; private RecordingStream recordingStream; private boolean classLevelDisableDiscovery = false; private boolean resetPerTest; @@ -104,6 +100,7 @@ public class HelidonTestNgListener implements IClassListener, ITestListener { private ConfigProviderResolver configProviderResolver; private Config config; private SeContainer container; + private PinningException pinningException; @Override public void onBeforeClass(ITestClass iTestClass) { @@ -371,12 +368,9 @@ private T getAnnotation(Class testClass, Class anno private void startRecordingStream() { if (pinnedThreadValidation) { - jfrVTPinned = new ArrayList<>(); recordingStream = new RecordingStream(); recordingStream.enable("jdk.VirtualThreadPinned").withStackTrace(); - recordingStream.onEvent("jdk.VirtualThreadPinned", event -> { - jfrVTPinned.add(new EventWrapper(event)); - }); + recordingStream.onEvent("jdk.VirtualThreadPinned", this::record); recordingStream.startAsync(); } } @@ -386,8 +380,8 @@ private void closeRecordingStream() { try { // Flush ending events recordingStream.stop(); - if (!jfrVTPinned.isEmpty()) { - fail("Some pinned virtual threads were detected:\n" + jfrVTPinned); + if (pinningException != null) { + throw pinningException; } } finally { recordingStream.close(); @@ -468,6 +462,15 @@ private static boolean hasAnnotation(AnnotatedElement element, Set testClass) implements Extension { @@ -683,28 +686,26 @@ private static final class SingletonLiteral extends AnnotationLiteral static final SingletonLiteral INSTANCE = new SingletonLiteral(); } - private static class EventWrapper { - + private static class PinningException extends AssertionError { private final RecordedEvent recordedEvent; - private EventWrapper(RecordedEvent recordedEvent) { + PinningException(RecordedEvent recordedEvent) { this.recordedEvent = recordedEvent; + if (recordedEvent.getStackTrace() != null) { + StackTraceElement[] stackTraceElements = recordedEvent.getStackTrace().getFrames().stream() + .map(f -> new StackTraceElement(f.getMethod().getType().getName(), + f.getMethod().getName(), + f.getMethod().getType().getName() + ".java", + f.getLineNumber())) + .toArray(StackTraceElement[]::new); + super.setStackTrace(stackTraceElements); + } } @Override - public String toString() { - StringBuilder builder = new StringBuilder(recordedEvent.toString()); - if (recordedEvent.getStackTrace() != null) { - builder.append("full-stackTrace = ["); - List frames = recordedEvent.getStackTrace().getFrames(); - for (RecordedFrame frame : frames) { - builder.append("\n\t").append(frame.getMethod().getType().getName()) - .append("#").append(frame.getMethod().getName()) - .append("(").append(frame.getLineNumber()).append(")"); - } - builder.append("\n]"); - } - return builder.toString(); + public String getMessage() { + return "Pinned virtual threads were detected:\n" + + recordedEvent.toString(); } }