From fa9e58bd4682e02dcbbe203d1b2bd328d9a50d4c Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Wed, 16 Sep 2026 16:34:05 +0200 Subject: [PATCH 1/5] Enforce continuation diagnostics in suite fixtures --- .../skills/fix-continuation-leakage/SKILL.md | 9 +- .../test/InstrumentationSpecification.groovy | 145 ++++++++++++------ .../scopediag/ScopeDiagnosticsExtension.java | 101 +++++++++++- .../scopediag/TrackScopeContinuations.java | 5 +- .../ScopeDiagnosticsSpockFixtureTest.groovy | 34 ++++ .../ScopeDiagnosticsJUnitFixtureTest.java | 41 +++++ 6 files changed, 278 insertions(+), 57 deletions(-) create mode 100644 dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy create mode 100644 dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java diff --git a/.agents/skills/fix-continuation-leakage/SKILL.md b/.agents/skills/fix-continuation-leakage/SKILL.md index d287bf19466..c6bcf52ac1a 100644 --- a/.agents/skills/fix-continuation-leakage/SKILL.md +++ b/.agents/skills/fix-continuation-leakage/SKILL.md @@ -69,11 +69,12 @@ set -o pipefail separately from trace-count or arrival-order assertions; fixing a leak may expose an unrelated flaky assertion. -## Fixture setup failures +## Fixture failures -Automatic recording may start after `setupSpec()` or equivalent fixture initialization. For an -initialization error or a trace wait inside setup, temporarily record around that setup block and -remove the diagnostic scaffolding after finding the owner. +Automatic recording covers Spock `setupSpec()` / `cleanupSpec()` and JUnit `@BeforeAll` / +`@AfterAll`, in addition to per-test setup and cleanup. The failure output identifies whether the +problem belongs to suite setup, one test, or suite cleanup. Code that runs before the +instrumentation-test harness initializes the tracer remains outside this window. Apply process-wide configuration before starting servers, actor systems, executors, or other long-lived fixtures. Use a forked test or recreate the fixture when its static state cannot be diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy index ed16a1f1f11..3ae4b4f8772 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy @@ -189,6 +189,9 @@ abstract class InstrumentationSpecification extends DDSpecification implements A @Shared boolean isLatestDepTest = Boolean.getBoolean('test.dd.latestDepTest') + @Shared + boolean scopeDiagnosticsSuiteSetupPending + @SuppressWarnings('PropertyName') @Shared TraceConfig MOCK_DSM_TRACE_CONFIG = new TraceConfig() { @@ -427,6 +430,11 @@ abstract class InstrumentationSpecification extends DDSpecification implements A // check for instrumentation issues during installation assert InstrumentationErrors.noErrors(): InstrumentationErrors.describeErrors() + + if (scopeDiagnosticsSuiteEnabled()) { + ScopeDiagnostics.startRecording() + scopeDiagnosticsSuiteSetupPending = true + } } protected String idGenerationStrategyName() { @@ -442,6 +450,18 @@ abstract class InstrumentationSpecification extends DDSpecification implements A } void setup() { + if (scopeDiagnosticsSuiteEnabled()) { + if (scopeDiagnosticsSuiteSetupPending) { + scopeDiagnosticsSuiteSetupPending = false + def suiteSetupFailure = reportScopeDiagnostics(scopeDiagClassConfig(), "suite setup") + if (suiteSetupFailure != null) { + throw suiteSetupFailure + } + } else { + ScopeDiagnostics.reset() + } + } + InstrumentationErrors.resetErrors() // reset for each test configureLoggingLevels() @@ -492,51 +512,57 @@ abstract class InstrumentationSpecification extends DDSpecification implements A } void cleanup() { - if (isTestAgentEnabled()) { - // save Datadog environment to DDAgentWriter header - addEnvironmentVariablesToHeaders(TEST_AGENT_API) - - // write ListWriter traces to the AgentWriter at cleanup so trace-processing changes occur after span assertions - def traces = TEST_WRITER.toArray() - for (trace in traces) { - TEST_AGENT_WRITER.write(trace as List) + try { + if (isTestAgentEnabled()) { + // save Datadog environment to DDAgentWriter header + addEnvironmentVariablesToHeaders(TEST_AGENT_API) + + // write ListWriter traces to the AgentWriter at cleanup so trace-processing changes occur after span assertions + def traces = TEST_WRITER.toArray() + for (trace in traces) { + TEST_AGENT_WRITER.write(trace as List) + } + TEST_AGENT_WRITER.flush() } - TEST_AGENT_WRITER.flush() - } - TEST_TRACER.flush() + TEST_TRACER.flush() - def scopeDiagnosticsFailure = reportScopeDiagnostics() + def scopeDiagnosticsFailure = reportScopeDiagnostics() - try { - def util = new MockUtil() - util.detachMock(STATS_D_CLIENT) + try { + def util = new MockUtil() + util.detachMock(STATS_D_CLIENT) - ActiveSubsystems.APPSEC_ACTIVE = originalAppSecRuntimeValue + ActiveSubsystems.APPSEC_ACTIVE = originalAppSecRuntimeValue - if (Config.get().isDebuggerCodeOriginEnabled()) { - injectSysConfig(CODE_ORIGIN_FOR_SPANS_ENABLED, "false", true) - rebuildConfig() - } + if (Config.get().isDebuggerCodeOriginEnabled()) { + injectSysConfig(CODE_ORIGIN_FOR_SPANS_ENABLED, "false", true) + rebuildConfig() + } - try { - if (enabledFinishTimingChecks()) { - doCheckRepeatedFinish() + try { + if (enabledFinishTimingChecks()) { + doCheckRepeatedFinish() + } + } finally { + spanFinishLocations.clear() + originalToTrackingSpan.clear() } - } finally { - spanFinishLocations.clear() - originalToTrackingSpan.clear() - } - // check for instrumentation issues while running each test - assert InstrumentationErrors.noErrors(): InstrumentationErrors.describeErrors() - } catch (Throwable cleanupFailure) { + // check for instrumentation issues while running each test + assert InstrumentationErrors.noErrors(): InstrumentationErrors.describeErrors() + } catch (Throwable cleanupFailure) { + if (scopeDiagnosticsFailure != null) { + cleanupFailure.addSuppressed(scopeDiagnosticsFailure) + } + throw cleanupFailure + } if (scopeDiagnosticsFailure != null) { - cleanupFailure.addSuppressed(scopeDiagnosticsFailure) + throw scopeDiagnosticsFailure + } + } finally { + if (scopeDiagnosticsSuiteEnabled()) { + ScopeDiagnostics.startRecording() } - throw cleanupFailure - } - if (scopeDiagnosticsFailure != null) { - throw scopeDiagnosticsFailure } } @@ -549,12 +575,23 @@ abstract class InstrumentationSpecification extends DDSpecification implements A return ann } + private TrackScopeContinuations scopeDiagClassConfig() { + return this.class.getAnnotation(TrackScopeContinuations) + } + + private boolean scopeDiagnosticsSuiteEnabled() { + return ScopeDiagnostics.isEnabled(scopeDiagClassConfig()) + } + private boolean scopeDiagnosticsEnabled() { return ScopeDiagnostics.isEnabled(scopeDiagConfig()) } private Throwable reportScopeDiagnostics() { - def config = scopeDiagConfig() + return reportScopeDiagnostics(scopeDiagConfig(), null) + } + + private Throwable reportScopeDiagnostics(TrackScopeContinuations config, String phase) { if (!ScopeDiagnostics.isEnabled(config)) { return null } @@ -563,6 +600,9 @@ abstract class InstrumentationSpecification extends DDSpecification implements A ScopeDiagnostics.stop() def report = ScopeDiagnostics.report() if (report.hasFindings()) { + if (phase != null) { + println("Scope diagnostics for ${phase}:") + } println(report.renderTimeline()) } ScopeDiagnostics.assertNoLeaks(report) @@ -602,20 +642,31 @@ abstract class InstrumentationSpecification extends DDSpecification implements A protected void cleanupAfterAgent() {} void cleanupSpec() { - TEST_TRACER?.close() - TEST_AGENT_WRITER?.close() + def scopeDiagnosticsFailure = reportScopeDiagnostics(scopeDiagClassConfig(), "suite cleanup") + try { + TEST_TRACER?.close() + TEST_AGENT_WRITER?.close() - if (null != activeTransformer) { - INSTRUMENTATION.removeTransformer(activeTransformer) - activeTransformer = null - } + if (null != activeTransformer) { + INSTRUMENTATION.removeTransformer(activeTransformer) + activeTransformer = null + } - cleanupAfterAgent() + cleanupAfterAgent() - // All cleanup should happen before these assertion. If not, a failing assertion may prevent cleanup - assert TRANSFORMED_CLASSES_TYPES.findAll { - GlobalIgnores.isAdditionallyIgnored(it.getActualName()) - }.isEmpty(): "Transformed classes match global libraries ignore matcher" + // All cleanup should happen before these assertion. If not, a failing assertion may prevent cleanup + assert TRANSFORMED_CLASSES_TYPES.findAll { + GlobalIgnores.isAdditionallyIgnored(it.getActualName()) + }.isEmpty(): "Transformed classes match global libraries ignore matcher" + } catch (Throwable cleanupFailure) { + if (scopeDiagnosticsFailure != null) { + cleanupFailure.addSuppressed(scopeDiagnosticsFailure) + } + throw cleanupFailure + } + if (scopeDiagnosticsFailure != null) { + throw scopeDiagnosticsFailure + } } boolean useStrictTraceWrites() { diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java index 7ba889284d0..b19f8181c68 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java @@ -1,20 +1,60 @@ package datadog.trace.agent.test.scopediag; +import datadog.trace.agent.test.AbstractInstrumentationTest; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; import java.util.Optional; +import org.junit.jupiter.api.extension.AfterAllCallback; import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; import org.junit.jupiter.api.extension.BeforeEachCallback; import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; import org.junit.platform.commons.support.AnnotationSupport; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Runs {@link ScopeDiagnostics} around each JUnit instrumentation test. */ -public final class ScopeDiagnosticsExtension implements BeforeEachCallback, AfterEachCallback { +/** Runs {@link ScopeDiagnostics} around JUnit instrumentation tests and suite fixtures. */ +public final class ScopeDiagnosticsExtension + implements BeforeAllCallback, + BeforeEachCallback, + AfterEachCallback, + AfterAllCallback, + InvocationInterceptor { private static final Logger log = LoggerFactory.getLogger(ScopeDiagnosticsExtension.class); + private boolean suiteEnabled; + private boolean suiteSetupPending; + + @Override + public void beforeAll(ExtensionContext context) { + suiteEnabled = ScopeDiagnostics.isEnabled(resolveClass(context)); + } + + @Override + public void interceptBeforeAllMethod( + Invocation invocation, + ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) + throws Throwable { + invocation.proceed(); + if (suiteEnabled && isHarnessLifecycleMethod(invocationContext)) { + ScopeDiagnostics.startRecording(); + suiteSetupPending = true; + } + } + @Override public void beforeEach(ExtensionContext context) { + if (suiteEnabled) { + if (suiteSetupPending) { + suiteSetupPending = false; + report(context.getRequiredTestClass().getSimpleName() + " suite setup"); + } else { + ScopeDiagnostics.reset(); + } + } TrackScopeContinuations config = resolve(context); if (ScopeDiagnostics.isEnabled(config)) { ScopeDiagnostics.startRecording(); @@ -23,16 +63,60 @@ public void beforeEach(ExtensionContext context) { @Override public void afterEach(ExtensionContext context) { - TrackScopeContinuations config = resolve(context); - if (!ScopeDiagnostics.isEnabled(config)) { + try { + TrackScopeContinuations config = resolve(context); + if (ScopeDiagnostics.isEnabled(config)) { + report(context.getDisplayName()); + } + } finally { + if (suiteEnabled) { + ScopeDiagnostics.startRecording(); + } + } + } + + @Override + public void interceptAfterAllMethod( + Invocation invocation, + ReflectiveInvocationContext invocationContext, + ExtensionContext extensionContext) + throws Throwable { + if (!suiteEnabled || !isHarnessLifecycleMethod(invocationContext)) { + invocation.proceed(); return; } + try { + report(extensionContext.getRequiredTestClass().getSimpleName() + " suite cleanup"); + } catch (Throwable diagnosticFailure) { + try { + invocation.proceed(); + } catch (Throwable cleanupFailure) { + cleanupFailure.addSuppressed(diagnosticFailure); + throw cleanupFailure; + } + throw diagnosticFailure; + } + invocation.proceed(); + } + + @Override + public void afterAll(ExtensionContext context) { + ScopeDiagnostics.reset(); + } + + private static boolean isHarnessLifecycleMethod( + ReflectiveInvocationContext invocationContext) { + return invocationContext.getExecutable().getDeclaringClass() + == AbstractInstrumentationTest.class; + } + + private static void report(String displayName) { try { ScopeDiagnostics.awaitQuiescence(); ScopeDiagnostics.stop(); ScopeDiagnosticsReport report = ScopeDiagnostics.report(); if (report.hasFindings()) { - log.info("[{}] {}", context.getDisplayName(), report.renderTimeline()); + log.info("[{}] {}", displayName, report.renderTimeline()); } ScopeDiagnostics.assertNoLeaks(report); } finally { @@ -55,4 +139,11 @@ private static TrackScopeContinuations resolve(ExtensionContext context) { .flatMap(c -> AnnotationSupport.findAnnotation(c, TrackScopeContinuations.class)) .orElse(null); } + + private static TrackScopeContinuations resolveClass(ExtensionContext context) { + return context + .getTestClass() + .flatMap(c -> AnnotationSupport.findAnnotation(c, TrackScopeContinuations.class)) + .orElse(null); + } } diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java index dcef5bb15a3..35751265359 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java @@ -6,7 +6,10 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -/** Configures the default-on scope and continuation diagnostic for a test class or method. */ +/** + * Configures the default-on scope and continuation diagnostic for a test class or method. + * Class-level configuration also applies to suite setup and cleanup. + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Inherited diff --git a/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy b/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy new file mode 100644 index 00000000000..ab9bcc604d0 --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy @@ -0,0 +1,34 @@ +package datadog.trace.agent.test.scopediag + +import datadog.trace.agent.test.InstrumentationSpecification +import datadog.trace.config.inversion.ConfigHelper + +class ScopeDiagnosticsSpockFixtureTest extends InstrumentationSpecification { + + @Override + void configurePreAgent() { + super.configurePreAgent() + ConfigHelper.get().setConfigInversionStrict(ConfigHelper.StrictnessPolicy.TEST) + } + + void setupSpec() { + recordResolvedContinuation("suite.setup") + } + + void cleanupSpec() { + recordResolvedContinuation("suite.cleanup") + } + + def "runs with suite fixture diagnostics"() { + expect: + true + } + + private void recordResolvedContinuation(String operationName) { + def span = TEST_TRACER.startSpan("test", operationName) + def continuation = TEST_TRACER.capture(span) + assert !ScopeDiagnostics.report().records().isEmpty(): "suite fixture continuation should be recorded" + continuation.release() + span.finish() + } +} diff --git a/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java new file mode 100644 index 00000000000..2bf505b62f6 --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java @@ -0,0 +1,41 @@ +package datadog.trace.agent.test.scopediag; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +import datadog.context.ContextContinuation; +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.bootstrap.instrumentation.api.AgentSpan; +import datadog.trace.config.inversion.ConfigHelper; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class ScopeDiagnosticsJUnitFixtureTest extends AbstractInstrumentationTest { + + static { + ConfigHelper.get().setConfigInversionStrict(ConfigHelper.StrictnessPolicy.TEST); + } + + @BeforeAll + static void recordSuiteSetupLifecycle() { + recordResolvedContinuation("suite.setup"); + } + + @AfterAll + static void recordSuiteCleanupLifecycle() { + recordResolvedContinuation("suite.cleanup"); + } + + @Test + void runsWithSuiteFixtureDiagnostics() {} + + private static void recordResolvedContinuation(String operationName) { + AgentSpan span = tracer.startSpan("test", operationName); + ContextContinuation continuation = tracer.capture(span); + assertFalse( + ScopeDiagnostics.report().records().isEmpty(), + "suite fixture continuation should be recorded"); + continuation.release(); + span.finish(); + } +} From e80e3d92b2f170d24b1d77809f642af45ce66ab7 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Thu, 17 Sep 2026 15:10:03 +0200 Subject: [PATCH 2/5] Clarify suite diagnostic reporting method --- .../trace/agent/test/InstrumentationSpecification.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy index 3ae4b4f8772..c3b707a982b 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy @@ -453,7 +453,7 @@ abstract class InstrumentationSpecification extends DDSpecification implements A if (scopeDiagnosticsSuiteEnabled()) { if (scopeDiagnosticsSuiteSetupPending) { scopeDiagnosticsSuiteSetupPending = false - def suiteSetupFailure = reportScopeDiagnostics(scopeDiagClassConfig(), "suite setup") + def suiteSetupFailure = reportScopeDiagnosticsForPhase(scopeDiagClassConfig(), "suite setup") if (suiteSetupFailure != null) { throw suiteSetupFailure } @@ -588,10 +588,10 @@ abstract class InstrumentationSpecification extends DDSpecification implements A } private Throwable reportScopeDiagnostics() { - return reportScopeDiagnostics(scopeDiagConfig(), null) + return reportScopeDiagnosticsForPhase(scopeDiagConfig(), null) } - private Throwable reportScopeDiagnostics(TrackScopeContinuations config, String phase) { + private Throwable reportScopeDiagnosticsForPhase(TrackScopeContinuations config, String phase) { if (!ScopeDiagnostics.isEnabled(config)) { return null } @@ -642,7 +642,7 @@ abstract class InstrumentationSpecification extends DDSpecification implements A protected void cleanupAfterAgent() {} void cleanupSpec() { - def scopeDiagnosticsFailure = reportScopeDiagnostics(scopeDiagClassConfig(), "suite cleanup") + def scopeDiagnosticsFailure = reportScopeDiagnosticsForPhase(scopeDiagClassConfig(), "suite cleanup") try { TEST_TRACER?.close() TEST_AGENT_WRITER?.close() From e602ab4cf9d4d2734694104c2baab5a7d1cbb54e Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Mon, 28 Sep 2026 15:55:51 +0200 Subject: [PATCH 3/5] fix autotest gaps --- .../instrumentation-testing/build.gradle | 1 + .../test/InstrumentationSpecification.groovy | 21 ++++- .../test/AbstractInstrumentationTest.java | 6 +- .../scopediag/ScopeDiagnosticsExtension.java | 20 ++++- .../ScopeDiagnosticsSpockExtension.java | 39 +++++++++ .../ScopeDiagnosticsSpockSupport.java | 6 ++ .../scopediag/TrackScopeContinuations.java | 8 +- .../ScopeDiagnosticsSpockFixtureTest.groovy | 43 ++++++++++ .../ScopeDiagnosticsExtensionTest.java | 83 +++++++++++++++++++ .../ScopeDiagnosticsSpockExtensionTest.java | 46 ++++++++++ 10 files changed, 266 insertions(+), 7 deletions(-) create mode 100644 dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java create mode 100644 dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockSupport.java create mode 100644 dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtensionTest.java create mode 100644 dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtensionTest.java diff --git a/dd-java-agent/instrumentation-testing/build.gradle b/dd-java-agent/instrumentation-testing/build.gradle index d26aa9176ea..fd3a9f63682 100644 --- a/dd-java-agent/instrumentation-testing/build.gradle +++ b/dd-java-agent/instrumentation-testing/build.gradle @@ -23,6 +23,7 @@ dependencies { testImplementation group: 'cglib', name: 'cglib', version: '3.2.5' // test instrumenting java 1.1 bytecode testImplementation group: 'net.sf.jt400', name: 'jt400', version: '6.1' + testImplementation libs.bundles.mockito // We have autoservices defined in test subtree, looks like we need this to be able to properly rebuild this testAnnotationProcessor libs.autoservice.processor diff --git a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy index c3b707a982b..a3fd9348ff9 100644 --- a/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy +++ b/dd-java-agent/instrumentation-testing/src/main/groovy/datadog/trace/agent/test/InstrumentationSpecification.groovy @@ -34,6 +34,7 @@ import datadog.metrics.impl.MonitoringImpl import datadog.trace.agent.test.asserts.ListWriterAssert import datadog.trace.agent.test.asserts.TagsAssert import datadog.trace.agent.test.scopediag.ScopeDiagnostics +import datadog.trace.agent.test.scopediag.ScopeDiagnosticsSpockSupport import datadog.trace.agent.test.scopediag.TrackScopeContinuations import datadog.trace.agent.test.datastreams.MockFeaturesDiscovery import datadog.trace.agent.test.datastreams.RecordingDatastreamsPayloadWriter @@ -113,7 +114,8 @@ import spock.lang.Shared @SuppressWarnings('UnnecessaryDotClass') @ExtendWith(TestClassShadowingExtension.class) @ExtendWith(TooManyInvocationsErrorHandler.class) -abstract class InstrumentationSpecification extends DDSpecification implements AgentBuilder.Listener { +@TrackScopeContinuations +abstract class InstrumentationSpecification extends DDSpecification implements AgentBuilder.Listener, ScopeDiagnosticsSpockSupport { private static final long TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(20) protected static final Instrumentation INSTRUMENTATION = ByteBuddyAgent.getInstrumentation() @@ -614,6 +616,23 @@ abstract class InstrumentationSpecification extends DDSpecification implements A } } + @Override + void onSuiteSetupFailure() { + if (!scopeDiagnosticsSuiteSetupPending) { + return + } + scopeDiagnosticsSuiteSetupPending = false + def diagnosticFailure = reportScopeDiagnosticsForPhase(scopeDiagClassConfig(), "suite setup") + try { + if (diagnosticFailure != null) { + throw diagnosticFailure + } + } finally { + // A pending setup window implies suite diagnostics are enabled. + ScopeDiagnostics.startRecording() + } + } + private void doCheckRepeatedFinish() { for (Map.Entry> entry: this.spanFinishLocations.entrySet()) { if (entry.value.size() == 1) { diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java index 2efee9eccbe..5a12a59167d 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/AbstractInstrumentationTest.java @@ -7,7 +7,7 @@ import datadog.instrument.classinject.ClassInjector; import datadog.trace.agent.test.assertions.TraceAssertions; import datadog.trace.agent.test.assertions.TraceMatcher; -import datadog.trace.agent.test.scopediag.ScopeDiagnosticsExtension; +import datadog.trace.agent.test.scopediag.TrackScopeContinuations; import datadog.trace.agent.tooling.AgentInstaller; import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.agent.tooling.TracerInstaller; @@ -56,11 +56,11 @@ * */ @WithConfig(key = "detailed.instrumentation.errors", value = "true") +@TrackScopeContinuations @ExtendWith({ TestClassShadowingExtension.class, AllowContextTestingExtension.class, - LegacyContextTestingExtension.class, - ScopeDiagnosticsExtension.class + LegacyContextTestingExtension.class }) public abstract class AbstractInstrumentationTest { static final Instrumentation INSTRUMENTATION = ByteBuddyAgent.getInstrumentation(); diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java index b19f8181c68..bfeaa6ee7e6 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java @@ -38,7 +38,25 @@ public void interceptBeforeAllMethod( ReflectiveInvocationContext invocationContext, ExtensionContext extensionContext) throws Throwable { - invocation.proceed(); + try { + invocation.proceed(); + } catch (Throwable setupFailure) { + if (suiteEnabled && suiteSetupPending) { + suiteSetupPending = false; + try { + report(extensionContext.getRequiredTestClass().getSimpleName() + " suite setup"); + } catch (Throwable diagnosticFailure) { + setupFailure.addSuppressed(diagnosticFailure); + } finally { + try { + ScopeDiagnostics.startRecording(); + } catch (Throwable diagnosticFailure) { + setupFailure.addSuppressed(diagnosticFailure); + } + } + } + throw setupFailure; + } if (suiteEnabled && isHarnessLifecycleMethod(invocationContext)) { ScopeDiagnostics.startRecording(); suiteSetupPending = true; diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java new file mode 100644 index 00000000000..f0aff0ef1e7 --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java @@ -0,0 +1,39 @@ +package datadog.trace.agent.test.scopediag; + +import org.spockframework.runtime.extension.IAnnotationDrivenExtension; +import org.spockframework.runtime.extension.IMethodInterceptor; +import org.spockframework.runtime.model.MethodInfo; +import org.spockframework.runtime.model.SpecInfo; + +/** Preserves suite-setup diagnostics when a Spock {@code setupSpec()} method fails. */ +public final class ScopeDiagnosticsSpockExtension + implements IAnnotationDrivenExtension { + private static final IMethodInterceptor SETUP_SPEC_INTERCEPTOR = + invocation -> { + try { + invocation.proceed(); + } catch (Throwable setupFailure) { + Object sharedInstance = invocation.getSharedInstance(); + if (sharedInstance == null) { + sharedInstance = invocation.getTarget(); + } + if (sharedInstance instanceof ScopeDiagnosticsSpockSupport) { + try { + ((ScopeDiagnosticsSpockSupport) sharedInstance).onSuiteSetupFailure(); + } catch (Throwable diagnosticFailure) { + setupFailure.addSuppressed(diagnosticFailure); + } + } + throw setupFailure; + } + }; + + @Override + public void visitSpecAnnotation(TrackScopeContinuations annotation, SpecInfo spec) { + for (MethodInfo setupSpec : spec.getBottomSpec().getAllSetupSpecMethods()) { + if (!setupSpec.getInterceptors().contains(SETUP_SPEC_INTERCEPTOR)) { + setupSpec.addInterceptor(SETUP_SPEC_INTERCEPTOR); + } + } + } +} diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockSupport.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockSupport.java new file mode 100644 index 00000000000..53a5344b127 --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockSupport.java @@ -0,0 +1,6 @@ +package datadog.trace.agent.test.scopediag; + +/** Receives failures intercepted around the Spock suite-setup lifecycle. */ +public interface ScopeDiagnosticsSpockSupport { + void onSuiteSetupFailure() throws Throwable; +} diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java index 35751265359..01f839d1e55 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/TrackScopeContinuations.java @@ -5,14 +5,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.junit.jupiter.api.extension.ExtendWith; +import org.spockframework.runtime.extension.ExtensionAnnotation; /** - * Configures the default-on scope and continuation diagnostic for a test class or method. - * Class-level configuration also applies to suite setup and cleanup. + * Enables and configures scope and continuation diagnostics for a JUnit or Spock test. Class-level + * configuration also applies to suite setup and cleanup. */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) @Inherited +@ExtendWith(ScopeDiagnosticsExtension.class) +@ExtensionAnnotation(ScopeDiagnosticsSpockExtension.class) public @interface TrackScopeContinuations { /** Set to {@code false} only for a proven incompatibility with the diagnostic itself. */ boolean enabled() default true; diff --git a/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy b/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy index ab9bcc604d0..93d77da566c 100644 --- a/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy +++ b/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy @@ -2,6 +2,8 @@ package datadog.trace.agent.test.scopediag import datadog.trace.agent.test.InstrumentationSpecification import datadog.trace.config.inversion.ConfigHelper +import spock.lang.Requires +import spock.lang.Specification class ScopeDiagnosticsSpockFixtureTest extends InstrumentationSpecification { @@ -24,6 +26,25 @@ class ScopeDiagnosticsSpockFixtureTest extends InstrumentationSpecification { true } + def "restarts recording after aborted suite setup"() { + given: + scopeDiagnosticsSuiteSetupPending = true + def span = TEST_TRACER.startSpan("test", "aborted.suite.setup") + def continuation = TEST_TRACER.capture(span) + + when: + onSuiteSetupFailure() + + then: + def failure = thrown(AssertionError) + failure.message.contains("Scope continuation problems detected") + ScopeDiagnostics.report().records().isEmpty() + + cleanup: + continuation.release() + span.finish() + } + private void recordResolvedContinuation(String operationName) { def span = TEST_TRACER.startSpan("test", operationName) def continuation = TEST_TRACER.capture(span) @@ -32,3 +53,25 @@ class ScopeDiagnosticsSpockFixtureTest extends InstrumentationSpecification { span.finish() } } + +@TrackScopeContinuations +abstract class ScopeDiagnosticsSpockFixtureBase extends Specification implements ScopeDiagnosticsSpockSupport { + @Override + void onSuiteSetupFailure() { + throw new AssertionError("scope continuation leaked") + } +} + +@Requires({ + System.getProperty("scope.diagnostics.run.failing.fixture") == "true" +}) +class FailingScopeDiagnosticsSpockFixture extends ScopeDiagnosticsSpockFixtureBase { + void setupSpec() { + throw new IllegalStateException("suite setup failed") + } + + def "is never run"() { + expect: + true + } +} diff --git a/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtensionTest.java b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtensionTest.java new file mode 100644 index 00000000000..a0c6020e7c8 --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtensionTest.java @@ -0,0 +1,83 @@ +package datadog.trace.agent.test.scopediag; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import datadog.context.Context; +import datadog.context.ContextContinuation; +import datadog.trace.agent.test.AbstractInstrumentationTest; +import datadog.trace.api.DDTraceId; +import java.lang.reflect.Method; +import java.util.Optional; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.InvocationInterceptor.Invocation; +import org.junit.jupiter.api.extension.ReflectiveInvocationContext; + +class ScopeDiagnosticsExtensionTest { + + private static class FailingFixture { + @BeforeAll + static void failSuiteSetup() {} + } + + @AfterEach + void tearDown() { + ScopeDiagnostics.reset(); + } + + @Test + @SuppressWarnings("unchecked") + void reportsPendingSuiteSetupBeforeStartingCleanupWindow() throws Throwable { + ScopeDiagnosticsExtension extension = new ScopeDiagnosticsExtension(); + ExtensionContext context = mock(ExtensionContext.class); + when(context.getTestClass()).thenReturn(Optional.of(FailingFixture.class)); + doReturn(FailingFixture.class).when(context).getRequiredTestClass(); + extension.beforeAll(context); + + Invocation harnessSetup = mock(Invocation.class); + ReflectiveInvocationContext harnessContext = mock(ReflectiveInvocationContext.class); + when(harnessContext.getExecutable()) + .thenReturn(AbstractInstrumentationTest.class.getDeclaredMethod("initAll")); + extension.interceptBeforeAllMethod(harnessSetup, harnessContext, context); + + ContextContinuation continuation = Context.root().capture(); + ScopeDiagnostics.recordCapture( + ScopeDiagnostics.recordingWindow(), + continuation, + DDTraceId.from(1), + 2, + "suite.setup", + (byte) 0); + + RuntimeException setupFailure = new RuntimeException("suite setup failed"); + Invocation failingSetup = mock(Invocation.class); + doThrow(setupFailure).when(failingSetup).proceed(); + ReflectiveInvocationContext failingContext = mock(ReflectiveInvocationContext.class); + when(failingContext.getExecutable()) + .thenReturn(FailingFixture.class.getDeclaredMethod("failSuiteSetup")); + + RuntimeException thrown = + assertThrows( + RuntimeException.class, + () -> extension.interceptBeforeAllMethod(failingSetup, failingContext, context)); + + assertSame(setupFailure, thrown); + assertEquals(1, thrown.getSuppressed().length); + assertInstanceOf(AssertionError.class, thrown.getSuppressed()[0]); + assertNotNull(ScopeDiagnostics.recordingWindow(), "suite cleanup must use a fresh window"); + assertTrue(ScopeDiagnostics.report().records().isEmpty()); + + continuation.release(); + } +} diff --git a/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtensionTest.java b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtensionTest.java new file mode 100644 index 00000000000..02ddfbeddac --- /dev/null +++ b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtensionTest.java @@ -0,0 +1,46 @@ +package datadog.trace.agent.test.scopediag; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; +import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; + +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.platform.launcher.LauncherDiscoveryRequest; +import org.junit.platform.launcher.core.LauncherFactory; +import org.junit.platform.launcher.listeners.SummaryGeneratingListener; +import org.junit.platform.launcher.listeners.TestExecutionSummary.Failure; + +class ScopeDiagnosticsSpockExtensionTest { + private static final String FAILING_FIXTURE = + "datadog.trace.agent.test.scopediag.FailingScopeDiagnosticsSpockFixture"; + private static final String RUN_FAILING_FIXTURE = "scope.diagnostics.run.failing.fixture"; + + @Test + void preservesSuiteSetupFailureAndSuppressesDiagnosticFailure() { + LauncherDiscoveryRequest discoveryRequest = + request().selectors(selectClass(FAILING_FIXTURE)).build(); + SummaryGeneratingListener listener = new SummaryGeneratingListener(); + + String previous = System.setProperty(RUN_FAILING_FIXTURE, "true"); + try { + LauncherFactory.create().execute(discoveryRequest, listener); + } finally { + if (previous == null) { + System.clearProperty(RUN_FAILING_FIXTURE); + } else { + System.setProperty(RUN_FAILING_FIXTURE, previous); + } + } + + List failures = listener.getSummary().getFailures(); + assertEquals(1, failures.size()); + Throwable setupFailure = failures.get(0).getException(); + assertInstanceOf(IllegalStateException.class, setupFailure); + assertEquals("suite setup failed", setupFailure.getMessage()); + assertEquals(1, setupFailure.getSuppressed().length); + assertInstanceOf(AssertionError.class, setupFailure.getSuppressed()[0]); + assertEquals("scope continuation leaked", setupFailure.getSuppressed()[0].getMessage()); + } +} From 8deb501691244632cddc78bba36bcd865970668b Mon Sep 17 00:00:00 2001 From: Yury Gribkov Date: Wed, 30 Sep 2026 23:53:48 -0700 Subject: [PATCH 4/5] Fix method-level scope diagnostic opt-outs in Spock (#12707) --- .../test/scopediag/ScopeDiagnosticsSpockExtension.java | 6 ++++++ .../test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java index f0aff0ef1e7..6984ee947bd 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockExtension.java @@ -2,6 +2,7 @@ import org.spockframework.runtime.extension.IAnnotationDrivenExtension; import org.spockframework.runtime.extension.IMethodInterceptor; +import org.spockframework.runtime.model.FeatureInfo; import org.spockframework.runtime.model.MethodInfo; import org.spockframework.runtime.model.SpecInfo; @@ -36,4 +37,9 @@ public void visitSpecAnnotation(TrackScopeContinuations annotation, SpecInfo spe } } } + + @Override + public void visitFeatureAnnotation(TrackScopeContinuations annotation, FeatureInfo feature) { + // Override Spock's default rejection of feature annotations so discovery succeeds. + } } diff --git a/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy b/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy index 93d77da566c..083c581bd4e 100644 --- a/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy +++ b/dd-java-agent/instrumentation-testing/src/test/groovy/datadog/trace/agent/test/scopediag/ScopeDiagnosticsSpockFixtureTest.groovy @@ -26,6 +26,12 @@ class ScopeDiagnosticsSpockFixtureTest extends InstrumentationSpecification { true } + @TrackScopeContinuations(enabled = false, reason = "synthetic fixture testing method-level opt-out") + def "runs with diagnostics disabled for this feature"() { + expect: + ScopeDiagnostics.recordingWindow() == null + } + def "restarts recording after aborted suite setup"() { given: scopeDiagnosticsSuiteSetupPending = true From 46d5db67b78aaccffc3f135a446c8b38817ee638 Mon Sep 17 00:00:00 2001 From: Andrea Marziali Date: Thu, 1 Oct 2026 14:50:13 +0200 Subject: [PATCH 5/5] Support nested --- .../test/scopediag/ScopeDiagnosticsExtension.java | 13 ++++++++++++- .../scopediag/ScopeDiagnosticsJUnitFixtureTest.java | 7 +++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java index bfeaa6ee7e6..6be91d70c50 100644 --- a/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java +++ b/dd-java-agent/instrumentation-testing/src/main/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsExtension.java @@ -119,7 +119,18 @@ public void interceptAfterAllMethod( @Override public void afterAll(ExtensionContext context) { - ScopeDiagnostics.reset(); + if (isOutermostTestClass(context)) { + ScopeDiagnostics.reset(); + } + } + + private static boolean isOutermostTestClass(ExtensionContext context) { + Class testClass = context.getRequiredTestClass(); + return !context + .getParent() + .flatMap(ExtensionContext::getTestClass) + .filter(parentClass -> parentClass != testClass) + .isPresent(); } private static boolean isHarnessLifecycleMethod( diff --git a/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java index 2bf505b62f6..1fb8446e690 100644 --- a/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java +++ b/dd-java-agent/instrumentation-testing/src/test/java/datadog/trace/agent/test/scopediag/ScopeDiagnosticsJUnitFixtureTest.java @@ -8,6 +8,7 @@ import datadog.trace.config.inversion.ConfigHelper; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class ScopeDiagnosticsJUnitFixtureTest extends AbstractInstrumentationTest { @@ -29,6 +30,12 @@ static void recordSuiteCleanupLifecycle() { @Test void runsWithSuiteFixtureDiagnostics() {} + @Nested + class NestedTest { + @Test + void keepsSuiteFixtureDiagnosticsRecording() {} + } + private static void recordResolvedContinuation(String operationName) { AgentSpan span = tracer.startSpan("test", operationName); ContextContinuation continuation = tracer.capture(span);