diff options
Diffstat (limited to 'vespa-osgi-testrunner/src/main/java')
3 files changed, 43 insertions, 41 deletions
diff --git a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/JunitRunner.java b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/JunitRunner.java index 06a4680e982..21890bab569 100644 --- a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/JunitRunner.java +++ b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/JunitRunner.java @@ -22,7 +22,6 @@ import java.time.Clock; import java.util.Collection; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.SortedMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentSkipListMap; @@ -33,8 +32,6 @@ import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; -import static java.util.stream.Collectors.toSet; - /** * @author mortent @@ -118,11 +115,9 @@ public class JunitRunner extends AbstractComponent implements TestRunner { List<Class<?>> testClasses = classLoader.apply(suite); if (testClasses == null) return null; - Set<String> testClassNames = testClasses.stream().map(Class::getName).collect(toSet()); testRuntimeProvider.initialize(testConfig); TestReportGeneratingListener testReportListener = new TestReportGeneratingListener(suite, - testClassNames, record -> logRecords.put(record.getSequenceNumber(), record), stdoutTee, stderrTee, @@ -156,12 +151,16 @@ public class JunitRunner extends AbstractComponent implements TestRunner { static TestRunner.Status testRunnerStatus(TestReport report) { if (report == null) return Status.NO_TESTS; - return switch (report.root().status()) { - case error, failed -> Status.FAILURE; - case inconclusive -> Status.INCONCLUSIVE; - case successful, skipped, aborted -> report.root().tally().containsKey(TestReport.Status.successful) ? Status.SUCCESS - : Status.NO_TESTS; - }; + switch (report.root().status()) { + case error: + case failed: return Status.FAILURE; + case inconclusive: return Status.INCONCLUSIVE; + case successful: + case skipped: + case aborted: return report.root().tally().containsKey(TestReport.Status.successful) ? Status.SUCCESS + : Status.NO_TESTS; + default: throw new IllegalStateException("unknown status '" + report.root().status() + "'"); + } } @Override diff --git a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReport.java b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReport.java index 0b25c6f6402..a2ac86309d9 100644 --- a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReport.java +++ b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReport.java @@ -20,7 +20,6 @@ import java.util.List; import java.util.Map; import java.util.Queue; import java.util.Set; -import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.LogRecord; @@ -33,31 +32,28 @@ public class TestReport { private final Object monitor = new Object(); private final Set<TestIdentifier> complete = new HashSet<>(); - private final Set<String> testClassNames; private final Clock clock; private final ContainerNode root; private final Suite suite; private NamedNode current; private TestPlan plan; - private TestReport(Clock clock, Suite suite, Set<String> testClassNames, ContainerNode root) { + private TestReport(Clock clock, Suite suite, ContainerNode root) { this.clock = clock; this.root = root; - this.testClassNames = Set.copyOf(testClassNames); this.current = root; this.suite = suite; } - TestReport(Clock clock, Suite suite, Set<String> testClassNames) { - this(clock, suite, testClassNames, new ContainerNode(null, null, toString(suite), clock.instant())); + TestReport(Clock clock, Suite suite) { + this(clock, suite, new ContainerNode(null, null, toString(suite), clock.instant())); } static TestReport createFailed(Clock clock, Suite suite, Throwable thrown) { - if (thrown instanceof OutOfMemoryError oome) throw oome; - if (thrown instanceof ExecutionException ee) thrown = ee.getCause(); - TestReport failed = new TestReport(clock, suite, Set.of()); + if (thrown instanceof OutOfMemoryError) throw (Error) thrown; + TestReport failed = new TestReport(clock, suite); failed.complete(); - failed.root().children.add(new FailureNode(failed.root(), clock.instant(), thrown, suite, Set.of())); + failed.root().children.add(new FailureNode(failed.root(), clock.instant(), thrown, suite)); return failed; } @@ -131,7 +127,7 @@ public class TestReport { synchronized (monitor) { Status status = Status.successful; if (thrown != null) { - FailureNode failure = new FailureNode(current, clock.instant(), thrown, suite, testClassNames); + FailureNode failure = new FailureNode(current, clock.instant(), thrown, suite); current.children.add(failure); status = failure.status(); } @@ -142,7 +138,7 @@ public class TestReport { void log(LogRecord record) { synchronized (monitor) { - if (record.getThrown() != null) trimStackTraces(record.getThrown(), testClassNames); + if (record.getThrown() != null) trimStackTraces(record.getThrown(), JunitRunner.class.getName()); if ( ! (current.children.peekLast() instanceof OutputNode)) current.children.add(new OutputNode(current)); @@ -162,9 +158,7 @@ public class TestReport { ContainerNode newRoot = new ContainerNode(null, null, root.name(), root.start()); newRoot.children.addAll(root.children); newRoot.children.addAll(other.root.children); - Set<String> testClassNames = new HashSet<>(this.testClassNames); - testClassNames.addAll(other.testClassNames); - TestReport merged = new TestReport(clock, suite, testClassNames, newRoot); + TestReport merged = new TestReport(clock, suite, newRoot); merged.complete(); return merged; } @@ -283,9 +277,9 @@ public class TestReport { private final Throwable thrown; private final Suite suite; - public FailureNode(NamedNode parent, Instant now, Throwable thrown, Suite suite, Set<String> testClassNames) { + public FailureNode(NamedNode parent, Instant now, Throwable thrown, Suite suite) { super(parent, null, thrown.toString(), now); - trimStackTraces(thrown, testClassNames); + trimStackTraces(thrown, JunitRunner.class.getName()); this.thrown = thrown; this.suite = suite; @@ -297,6 +291,10 @@ public class TestReport { children.add(child); } + public Throwable thrown() { + return thrown; + } + @Override public Duration duration() { return Duration.ZERO; @@ -329,20 +327,26 @@ public class TestReport { /** * Recursively trims stack traces for the given throwable and its causes/suppressed. - * This is based on the assumption that the relevant stack is anything from the first - * test bundle class frame, and upwards; the exception is for dynamic tests, where a - * specific dynamic test factory method is right below the first user frame. + * This is based on the assumption that the relevant stack is anything above the first native + * reflection invocation, above any frame in the given root class. */ - static void trimStackTraces(Throwable thrown, Set<String> testClassNames) { + static void trimStackTraces(Throwable thrown, String testFrameworkRootClass) { if (thrown == null) return; StackTraceElement[] stack = thrown.getStackTrace(); - int i = -1; - int cutoff = stack.length; + int i = 0; + int firstReflectFrame = -1; + int cutoff = 0; + boolean rootedInTestFramework = false; while (++i < stack.length) { - for (String name : testClassNames) if (stack[i].getClassName().startsWith(name)) - cutoff = i + 1; + rootedInTestFramework |= testFrameworkRootClass.equals(stack[i].getClassName()); + if (firstReflectFrame == -1 && stack[i].getClassName().startsWith("jdk.internal.reflect.")) + firstReflectFrame = i; // jdk.internal.reflect class invokes the first user test frame, on both jdk 17 and 21. + if (rootedInTestFramework && firstReflectFrame > 0) { + cutoff = firstReflectFrame; + break; + } boolean isDynamicTestInvocation = "org.junit.jupiter.engine.descriptor.DynamicTestTestDescriptor".equals(stack[i].getClassName()); if (isDynamicTestInvocation) { cutoff = i; @@ -352,9 +356,9 @@ public class TestReport { thrown.setStackTrace(copyOf(stack, cutoff)); for (Throwable suppressed : thrown.getSuppressed()) - trimStackTraces(suppressed, testClassNames); + trimStackTraces(suppressed, testFrameworkRootClass); - trimStackTraces(thrown.getCause(), testClassNames); + trimStackTraces(thrown.getCause(), testFrameworkRootClass); } private static String toString(Suite suite) { diff --git a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReportGeneratingListener.java b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReportGeneratingListener.java index 487cc8c501a..5bc9fda6835 100644 --- a/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReportGeneratingListener.java +++ b/vespa-osgi-testrunner/src/main/java/com/yahoo/vespa/testrunner/TestReportGeneratingListener.java @@ -18,7 +18,6 @@ import java.time.Clock; import java.time.ZoneOffset; import java.util.HashMap; import java.util.Map; -import java.util.Set; import java.util.function.Consumer; import java.util.logging.Handler; import java.util.logging.Level; @@ -42,8 +41,8 @@ class TestReportGeneratingListener implements TestExecutionListener { private final Handler handler; // Captures logging from test code. private final Clock clock; - TestReportGeneratingListener(Suite suite, Set<String> testClassNames, Consumer<LogRecord> logger, TeeStream stdoutTee, TeeStream stderrTee, Clock clock) { - this.report = new TestReport(clock, suite, testClassNames); + TestReportGeneratingListener(Suite suite, Consumer<LogRecord> logger, TeeStream stdoutTee, TeeStream stderrTee, Clock clock) { + this.report = new TestReport(clock, suite); this.logger = logger; this.stdoutTee = stdoutTee; this.stderrTee = stderrTee; |