Skip to content

Commit

Permalink
Print stacktraces in a better way
Browse files Browse the repository at this point in the history
Signed-off-by: Jorge Bescos Gascon <[email protected]>
  • Loading branch information
jbescos committed Dec 10, 2024
1 parent e6b3a66 commit 32c41f6
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,79 +16,77 @@

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<EventWrapper> 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();
}
}
}

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<RecordedFrame> 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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
*/
Expand All @@ -93,7 +90,6 @@ public class HelidonTestNgListener implements IClassListener, ITestListener {
private List<AddExtension> classLevelExtensions = new ArrayList<>();
private List<AddBean> classLevelBeans = new ArrayList<>();
private ConfigMeta classLevelConfigMeta = new ConfigMeta();
private List<EventWrapper> jfrVTPinned;
private RecordingStream recordingStream;
private boolean classLevelDisableDiscovery = false;
private boolean resetPerTest;
Expand All @@ -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) {
Expand Down Expand Up @@ -371,12 +368,9 @@ private <T extends Annotation> T getAnnotation(Class<?> testClass, Class<T> 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();
}
}
Expand All @@ -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();
Expand Down Expand Up @@ -468,6 +462,15 @@ private static boolean hasAnnotation(AnnotatedElement element, Set<Class<? exten
return false;
}

void record(RecordedEvent event) {
PinningException e = new PinningException(event);
if (pinningException == null) {
pinningException = e;
} else {
pinningException.addSuppressed(e);
}
}

@SuppressWarnings("CdiManagedBeanInconsistencyInspection")
private record TestInstanceExtension(Object testInstance, Class<?> testClass) implements Extension {

Expand Down Expand Up @@ -683,28 +686,26 @@ private static final class SingletonLiteral extends AnnotationLiteral<Singleton>
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<RecordedFrame> 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();
}
}

Expand Down

0 comments on commit 32c41f6

Please sign in to comment.